Fail2ban and basic server hardening
The first time I checked auth logs on a fresh VPS, I was genuinely surprised. The server had been online for less than an hour and there were already dozens of failed login attempts from IPs I had never seen. Bots scan the internet constantly, hammering port 22 on every public IP they find. A default SSH configuration is an open invitation.
Here is what I do on every new server before I deploy anything.
Disable root login
The root user exists on every Linux system. Attackers know this. Brute-forcing root over SSH is the lowest-effort attack there is, and it works if you have a weak password.
Create a regular user with sudo privileges if you do not already have one:
adduser jeremy
usermod -aG sudo jeremyThen edit the SSH daemon configuration:
sudo nano /etc/ssh/sshd_configFind the PermitRootLogin line and change it:
PermitRootLogin no
Restart the SSH service for the change to take effect:
sudo systemctl restart sshdFrom now on, root cannot log in over SSH. You log in as your regular user and use sudo when you need elevated privileges.
One note on PasswordAuthentication: ideally you would disable password-based login entirely and use SSH keys instead. I plan to cover that setup in a future post on SSH keys. For now, a strong password and the other measures in this post go a long way.
Set up UFW
UFW (Uncomplicated Firewall) is a frontend for iptables that makes firewall rules readable. It comes preinstalled on Ubuntu and Pop!_OS.
The default strategy is simple: deny everything incoming, allow everything outgoing, then poke holes for the services you actually run.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow httpsImportant: allow SSH before you enable the firewall. If you skip that step on a remote server, you lock yourself out.
sudo ufw enableCheck that everything looks right:
sudo ufw statusYou should see something like:
Status: active
To Action From
-- ------ ----
22/tcp ALLOW Anywhere
80/tcp ALLOW Anywhere
443/tcp ALLOW Anywhere
22/tcp (v6) ALLOW Anywhere (v6)
80/tcp (v6) ALLOW Anywhere (v6)
443/tcp (v6) ALLOW Anywhere (v6)
That is it. Only SSH, HTTP, and HTTPS traffic gets through. Everything else is dropped silently.
Install and configure Fail2ban
Fail2ban watches log files for repeated failed authentication attempts and bans offending IPs by adding firewall rules. It is the single most effective thing you can install against brute force attacks.
sudo apt install fail2banDo not edit /etc/fail2ban/jail.conf directly. It gets overwritten on updates. Create a local override instead:
sudo nano /etc/fail2ban/jail.local[DEFAULT]
bantime = 10m
findtime = 10m
maxretry = 5
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 1h
findtime = 10mHere is what those values mean:
maxretry = 3: three failed login attempts triggers a banfindtime = 10m: those attempts must happen within a 10-minute windowbantime = 1h: banned IPs are blocked for one hour
I set the SSH jail stricter than the defaults. Three failed attempts in 10 minutes is not a human mistyping a password. It is a bot.
Start and enable the service:
sudo systemctl enable fail2ban
sudo systemctl start fail2banCheck the status of your SSH jail:
sudo fail2ban-client status sshdAfter a day or two, you will see banned IPs accumulating. It is a little unsettling how quickly the list grows.
Enable unattended security updates
Unpatched software is how servers get compromised. The unattended-upgrades package automatically installs security updates so you do not have to remember to do it manually.
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgradesSelect "Yes" when prompted. This creates the configuration at /etc/apt/apt.conf.d/20auto-upgrades and enables automatic security updates.
By default, it only installs security patches, not full distribution upgrades. That is exactly what you want. Security fixes get applied automatically. Everything else you handle manually when you are ready.
Logs go to /var/log/unattended-upgrades/ if you ever want to check what was installed.
Checking auth logs
After a few days, check your auth logs to see what Fail2ban is protecting you from:
sudo cat /var/log/auth.log | grep "Failed password"Or if your system uses systemd journal:
journalctl -u ssh | grep "Failed"You will see lines like:
Failed password for root from 203.0.113.42 port 54321 ssh2
Failed password for invalid user admin from 198.51.100.7 port 12345 ssh2
Common usernames the bots try: root, admin, ubuntu, test, user, postgres, deploy. They cycle through dictionaries of passwords against each one. This happens on every server with a public IP. It is not personal. It is automated.
With root login disabled, a firewall in place, and Fail2ban banning repeat offenders, your server is no longer the easy target these bots are looking for. They move on to the next IP.
Sources
Related posts
Why I self-host my passwords with Vaultwarden
The case for running Vaultwarden instead of using a proprietary password manager, and how to set it up.
Setting up SSH keys and never typing a password again
How to set up SSH key authentication, manage multiple keys, and configure your SSH client for a smoother workflow.
Enjoying the blog? Subscribe via RSS to get new posts in your reader.
Subscribe via RSS