After setting up my VPS, security was the next priority. A server on the public internet gets probed constantly. Within hours of going live, the auth log fills up with failed SSH login attempts from all over the world.

This post covers the measures I took. None of this is novel, but having it written down in one place is useful.

SSH hardening

SSH is the primary way you access a Linux server, which makes it the primary target for attackers. The default configuration is functional but permissive.

Disable root login

Root login over SSH should be off. You should connect as a regular user and use sudo when needed.

Edit /etc/ssh/sshd_config:

PermitRootLogin no

Disable password authentication

Key-based authentication is both more convenient and more secure than passwords. Once your public key is set up, disable password login entirely:

PasswordAuthentication no
PubkeyAuthentication yes

Make sure your key is working before you apply this. Otherwise you will lock yourself out.

Change the default port (optional)

Moving SSH to a non-standard port reduces noise in the logs. It is not real security, but it cuts automated scan attempts significantly:

Port 2222

Pick any unused port above 1024. Remember to update your firewall rules to allow the new port before restarting SSH.

Other useful settings

A few more options worth setting:

MaxAuthTries 3
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
  • MaxAuthTries 3 limits authentication attempts per connection.
  • LoginGraceTime 30 closes the connection if authentication is not completed in 30 seconds.
  • ClientAliveInterval and ClientAliveCountMax disconnect idle sessions after about 10 minutes.

After making changes:

sshd -t  # test the configuration
systemctl restart sshd

Always test the configuration before restarting. A syntax error in sshd_config can lock you out.

Fail2ban

Fail2ban monitors log files and bans IP addresses that show malicious behavior. It is particularly useful for SSH, where brute-force attempts are constant.

Installation

apt install fail2ban

Configuration

Fail2ban uses jail configurations. Create a local override file so your changes survive package updates:

cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Edit /etc/fail2ban/jail.local. The key settings for SSH:

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600

This bans an IP for one hour after 3 failed login attempts within 10 minutes. You can increase bantime for more aggressive banning.

If you changed the SSH port, update the port value:

port = 2222

Start and enable the service:

systemctl enable fail2ban
systemctl start fail2ban

Checking status

fail2ban-client status sshd

This shows the number of currently banned IPs and total bans since the service started. On a public server, you will see bans accumulating within minutes.

To unban an IP manually:

fail2ban-client set sshd unbanip 203.0.113.50

UFW firewall

UFW (Uncomplicated Firewall) is a frontend for iptables that makes basic firewall management straightforward.

Installation and setup

UFW is often pre-installed on Debian/Ubuntu. If not:

apt install ufw

Start by setting the default policies:

ufw default deny incoming
ufw default allow outgoing

This blocks all incoming connections except those you explicitly allow. Outgoing traffic is unrestricted.

Allowing services

Allow the services you need:

ufw allow 22/tcp    # SSH (or your custom port)
ufw allow 80/tcp    # HTTP
ufw allow 443/tcp   # HTTPS

If you moved SSH to a different port:

ufw allow 2222/tcp

Enabling the firewall

ufw enable

UFW will warn you that this may disrupt existing SSH connections. Make sure you have allowed SSH before enabling.

Check the current rules:

ufw status verbose

Rate limiting

UFW has a built-in rate limiting feature for SSH:

ufw limit 22/tcp

This allows a maximum of 6 connections per 30 seconds from a single IP. It is a simple complement to fail2ban.

Unattended upgrades

Security updates should be applied promptly. On a personal server that you might not check every day, automatic security updates make sense.

Installation

apt install unattended-upgrades apt-listchanges

Configuration

Enable automatic updates:

dpkg-reconfigure -plow unattended-upgrades

This creates /etc/apt/apt.conf.d/20auto-upgrades with:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

The default configuration in /etc/apt/apt.conf.d/50unattended-upgrades is already set to install security updates. You can verify:

grep -A 5 "Allowed-Origins" /etc/apt/apt.conf.d/50unattended-upgrades

It should include lines for security updates like:

"${distro_id}:${distro_codename}-security";

Optional: email notifications

If you want to receive email when updates are applied, set the mail address in 50unattended-upgrades:

Unattended-Upgrade::Mail "martin@packetlog.org";

You will need a working mail setup on the server for this.

Optional: automatic reboot

Some kernel updates require a reboot. You can configure unattended-upgrades to reboot automatically at a specific time:

Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

I leave this off and reboot manually when needed. I prefer to know when my server restarts.

Testing

Run a dry-run to verify the configuration:

unattended-upgrades --dry-run --debug

Additional measures

A few more things I do that are quick and worthwhile.

Remove unnecessary packages

A fresh Debian install is already minimal, but check for anything you do not need:

apt list --installed | wc -l

The fewer packages installed, the smaller the attack surface.

Check listening ports

Periodically verify that only expected services are listening:

ss -tlnp

This shows all TCP ports in listening state along with the process using them. If you see something unexpected, investigate.

Set up log monitoring

Review logs periodically. The important ones:

journalctl -u sshd --since "1 hour ago"
tail -100 /var/log/auth.log

Failed login attempts, unusual process activity, and unexpected network connections are all worth investigating.

Keep backups

This is not a security hardening step per se, but it is the most important thing you can do. If something goes wrong, whether from an attack or a misconfiguration, backups let you recover.

I keep automated daily backups of the server’s data to an external storage location. A simple cron job with rsync or rclone works well for this.

Summary

The measures here are the baseline. They will not stop a determined, targeted attacker, but they handle the vast majority of real-world threats to a personal server: automated scans, brute-force attempts, and unpatched vulnerabilities.

The key points:

  • SSH: key-only auth, no root login, rate limiting
  • Fail2ban: automatic banning of repeat offenders
  • UFW: deny by default, allow only what is needed
  • Unattended upgrades: automatic security patches

All of this takes about 30 minutes to set up on a fresh server. The Debian security documentation and the fail2ban wiki are good references for going deeper.