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
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
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:
- Installed packages and their versions (looking for deprecated dependencies)
- Open ports and running services
- SSH key access (removing old keys)
- fail2ban statistics and recent bans
- Disk space and backups
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