VPS Security Hardening: Essential Steps to Protect Your Self-Hosted Infrastructure

VPS Security Hardening: Essential Steps to Protect Your Self-Hosted Infrastructure

When I first rented a VPS for self-hosting, I treated it like a fresh Linux install and called it a day. That was naive. Within hours, my server logs showed dozens of SSH brute-force attempts per minute. That's when I learned that default configurations are targets, not starting points. Hardening a VPS isn't optional—it's fundamental to keeping your data, services, and reputation safe.

I've now locked down multiple VPS instances across RackNerd, Hetzner, and Contabo, and the pattern is always the same: systematic layering of defenses. A basic $40/year VPS from any reputable provider can be made as secure as much more expensive infrastructure when you apply these steps correctly.

Update Everything First

Before anything else, update your system packages. This closes known vulnerabilities in the base OS and installed utilities. I run this immediately after spinning up any new VPS:

sudo apt update && sudo apt upgrade -y
sudo apt autoremove -y
sudo reboot

The reboot ensures kernel updates take effect. Don't skip this step even if nothing appears to have changed—critical security patches often ship without fanfare. I've seen servers compromised within 24 hours because a known vulnerability existed in an outdated package.

Disable Root Login and Use SSH Keys

Password authentication is the weakest link. I disable it entirely and use SSH keys instead. Create a non-root user first if your VPS provider hasn't already:

sudo useradd -m -s /bin/bash deploy
sudo usermod -aG sudo deploy
sudo mkdir -p /home/deploy/.ssh
sudo chmod 700 /home/deploy/.ssh

Copy your public key to the server (do this from your local machine, not on the server):

cat ~/.ssh/id_rsa.pub | ssh root@your_vps_ip "cat >> /root/.ssh/authorized_keys"

Then edit SSH configuration to harden it:

sudo nano /etc/ssh/sshd_config

Change or add these lines:

Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
X11Forwarding no
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30

I change the port from 22 to something non-standard like 2222. This doesn't stop a determined attacker, but it eliminates 95% of the bot traffic trying default ports. Restart SSH to apply changes:

sudo systemctl restart ssh

Test login from your local machine with the new port before closing your current session:

ssh -i ~/.ssh/id_rsa -p 2222 deploy@your_vps_ip
Watch out: Never log out of your current SSH session until you've verified you can log back in. Lockouts are frustrating and require provider intervention.

Set Up the Firewall with UFW

UFW (Uncomplicated Firewall) is straightforward and powerful. I prefer it over raw iptables because the syntax is memorable:

sudo apt install ufw -y
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 2222/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Only allow ports you actually need. If you're running a web service, 80 and 443 are essential. If you're running Docker with internal services, you might not expose much at all. View the firewall status:

sudo ufw status verbose

I check this regularly when adding new services. A misconfigured firewall rule can silently block a service you've spent hours setting up.

Install and Configure fail2ban

fail2ban watches logs and blocks IPs that make repeated failed attempts. It's essential for SSH on a public VPS:

sudo apt install fail2ban -y
sudo systemctl enable fail2ban

Create a local configuration file so updates don't overwrite your settings:

sudo nano /etc/fail2ban/jail.local

Add this configuration:

[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3

[sshd]
enabled = true
port = 2222
logpath = /var/log/auth.log
maxretry = 3
bantime = 7200

This bans IPs for 2 hours after 3 failed login attempts within 10 minutes. Restart the service:

sudo systemctl restart fail2ban

Monitor fail2ban activity with:

sudo fail2ban-client status sshd
Tip: Check your fail2ban logs occasionally: sudo tail -f /var/log/fail2ban.log. You'll see exactly which IPs are being blocked and get a sense of attack patterns.

Harden the Kernel with sysctl

The kernel can be configured to mitigate several attack vectors. I apply these tunings on every VPS:

sudo nano /etc/sysctl.conf

Add or uncomment these lines:

# Kernel hardening
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
kernel.unprivileged_ns_clone = 0
kernel.unprivileged_userns_clone = 0
kernel.kexec_load_disabled = 1
kernel.yama.ptrace_scope = 3

# Network hardening
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.log_martians = 1
net.ipv6.conf.all.disable_ipv6 = 0
net.ipv4.tcp_timestamps = 1

Apply the changes immediately:

sudo sysctl -p

Enable Automatic Security Updates

I never want to manually apply security patches. Set up unattended-upgrades:

sudo apt install unattended-upgrades apt-listchanges -y
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades

Uncomment and modify this line to enable automatic reboots if needed:

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

This schedules automatic reboots at 3 AM UTC if kernel updates require it. Test the configuration:

sudo unattended-upgrade -d

Set Up Log Monitoring

Logs are evidence of attacks and misconfigurations. I periodically review auth logs for suspicious patterns:

sudo tail -100 /var/log/auth.log | grep "Failed password"

For continuous monitoring, install and configure Auditd:

sudo apt install auditd -y
sudo systemctl enable auditd

This logs system calls and file access at the kernel level, providing deeper insight into what's happening on your system.

Consider a VPS with Good DDoS Protection

Most budget VPS providers, including RackNerd's sub-$50/year offerings, don't include DDoS mitigation by default. If you're running a public service, this matters. I've experienced small DDoS attacks against self-hosted services, and they can take down an unprotected VPS instantly.

Providers like Hetzner and Contabo offer optional DDoS protection at reasonable cost. It's worth evaluating for your use case.

Regular Security Audits

Hardening isn't a one-time task. I schedule quarterly audits where I check:

A simple bash script can help automate this:

#!/bin/bash
echo "=== System Updates Available ==="
sudo apt update && apt list --upgradable

echo "=== Open Ports ==="
sudo ss -tlnp

echo "=== fail2ban Status ==="
sudo fail2ban-client status sshd

echo "=== Disk Usage ==="
df -h

Conclusion

Security hardening is systematic, not mystical. The steps I've outlined—key-based authentication, firewall rules, fail2ban, kernel tuning, and automatic updates—form a solid defensive layer for any self-hosted VPS, whether you're running Nextcloud, Jellyfin, or a personal Docker deployment.

Start with the SSH key setup and UFW firewall today. Those two changes eliminate the majority of common attacks. Then layer on fail2ban and sysctl hardening. By the time you're done, your $40/year VPS will be significantly more secure than a default installation—and more secure than many servers in production.

Next steps: Apply these hardening steps to your VPS, test your SSH access immediately, and bookmark your firewall rules for the next time you deploy a service.

Discussion

```