Most servers get compromised not through sophisticated zero-day exploits, but through basic misconfigurations, forgotten defaults, and skipped updates. A freshly provisioned Linux server is not a secure server. It's a starting point.
This guide walks through the hardening steps that actually matter — the ones that close off the most common attack vectors on production systems.
Start with User and SSH Access
SSH is the front door to your server. It's also the most commonly probed service on the internet. Bots are scanning for open port 22 right now, trying default credentials and known exploits.
Disable Root Login and Password Authentication
The first two changes you should make on any new server:
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yesDisabling root login forces attackers to know both a valid username and have the correct SSH key. Combined with disabling password auth entirely, you eliminate brute-force attacks from the equation.
After editing, reload the daemon:
systemctl reload sshdChange the Default SSH Port
This is security through obscurity — not a real defense on its own, but it eliminates enormous amounts of automated noise from your logs:
# In /etc/ssh/sshd_config Port 2222Remember to open the new port in your firewall before reloading SSH, or you'll lock yourself out.
Create a Dedicated Admin User
Never use a generic username like admin or ubuntu for day-to-day access. Create a named user with sudo privileges:
useradd -m -s /bin/bash yourname usermod -aG sudo yourname mkdir -p /home/yourname/.ssh cp ~/.ssh/authorized_keys /home/yourname/.ssh/ chown -R yourname:yourname /home/yourname/.ssh chmod 700 /home/yourname/.ssh chmod 600 /home/yourname/.ssh/authorized_keysFirewall Configuration with UFW
A firewall is non-negotiable. On Ubuntu and Debian systems, UFW gives you a straightforward interface to iptables that's hard to get wrong.
The default posture should be: deny everything, then explicitly allow what you need.
ufw default deny incoming ufw default allow outgoing ufw allow 2222/tcp # Your custom SSH port ufw allow 80/tcp ufw allow 443/tcp ufw enableCheck the status at any time:
ufw status verboseIf you're running a database like MySQL or PostgreSQL, do not open those ports publicly. They should only be accessible via localhost or a private network interface. A PostgreSQL instance exposed on port 5432 to the internet is an invitation for trouble.
Keep the System Updated — Automatically
Unpatched packages are responsible for a disproportionate share of real-world compromises. The fix is simple: keep things updated.
Enable Unattended Upgrades
On Debian/Ubuntu:
apt install unattended-upgrades dpkg-reconfigure --priority=low unattended-upgradesThe key configuration lives in /etc/apt/apt.conf.d/50unattended-upgrades. At minimum, enable automatic security updates:
Unattended-Upgrade::Allowed-Origins { "${distro_id}:${distro_codename}-security"; }; Unattended-Upgrade::Automatic-Reboot "false"; Unattended-Upgrade::Mail "you@yourdomain.com";Setting Automatic-Reboot to false on production systems lets you control when kernel reboots happen — important for uptime. You'll still get an email when a reboot is needed.
On managed hosting plans, kernel updates and OS-level patching are typically handled for you. But if you're managing your own VPS, unattended upgrades are not optional.
Harden the Kernel with sysctl
The Linux kernel exposes a lot of network and memory behavior through sysctl. Several defaults are fine for development but should be tightened on production servers.
Add the following to /etc/sysctl.d/99-hardening.conf:
# Disable IP source routing net.ipv4.conf.all.accept_source_route = 0 net.ipv6.conf.all.accept_source_route = 0 # Ignore ICMP redirects net.ipv4.conf.all.accept_redirects = 0 net.ipv6.conf.all.accept_redirects = 0 # Enable SYN flood protection net.ipv4.tcp_syncookies = 1 # Disable IP forwarding (unless this is a router) net.ipv4.ip_forward = 0 # Protect against time-wait assassination net.ipv4.tcp_rfc1337 = 1 # Restrict dmesg to root kernel.dmesg_restrict = 1 # Prevent core dumps from setuid programs fs.suid_dumpable = 0Apply the changes immediately:
sysctl -p /etc/sysctl.d/99-hardening.confAudit Running Services
Every service running on your server is a potential attack surface. Audit what's actually running:
ss -tulnpThis shows all listening sockets with the associated process. Go through the output line by line. If you don't recognize a service, investigate it. If you don't need it, disable it:
systemctl stop service-name systemctl disable service-nameCommon offenders on default installs include: avahi-daemon, cups, and various Bluetooth services — none of which belong on a headless production server.
Set Up Fail2ban for Intrusion Detection
Fail2ban monitors log files and temporarily bans IPs that show malicious behavior — repeated failed SSH logins, for example.
apt install fail2banCreate a local override file (so your config survives package updates):
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.localThen configure the SSH jail in /etc/fail2ban/jail.local:
[sshd] enabled = true port = 2222 filter = sshd logpath = /var/log/auth.log maxretry = 4 bantime = 3600 findtime = 600This bans any IP that fails SSH authentication 4 times within 10 minutes, for one hour. Adjust these thresholds to match your risk tolerance.
Backups: Your Last Line of Defense
Hardening reduces your attack surface, but no hardening is perfect. If something does go wrong — a compromised credential, a zero-day, a misconfiguration — backups are what let you recover without catastrophic data loss.
The key metrics for any backup strategy are RPO (Recovery Point Objective) and RTO (Recovery Time Objective). RPO defines how much data you can afford to lose; RTO defines how fast you need to be back online.
A single daily backup gives you an RPO of up to 24 hours. For most production websites that's acceptable, but for high-transaction applications — e-commerce, SaaS products, anything writing data continuously — it often isn't. Running backups multiple times a day (we support up to four per day, with configurable full or partial runs) dramatically shrinks that window without proportionally increasing storage costs, since intra-day runs can be partial backups covering only databases and changed files.
Whatever backup strategy you choose, verify it regularly. A backup you've never tested is a backup you can't trust.
A Hardened Server Is a Maintained Server
Hardening isn't a one-time task you check off and forget. It's an ongoing practice. Services change, new CVEs drop, and configurations drift over time — especially on servers touched by multiple people.
Schedule a quarterly audit: review running services, check SSH config, verify firewall rules are still accurate, and confirm your backups are working. Tools like lynis can automate much of this:
apt install lynis lynis audit systemLynis scores your system and gives specific, prioritized recommendations. It's not a replacement for understanding what you're doing, but it's an excellent sanity check.
The servers that get compromised are almost always the ones nobody was actively watching. Stay deliberate, stay current, and treat security as infrastructure — not an afterthought.