How to secure a Linux VPS: fail2ban, firewall, and SSH hardening
11 min read · 25-Oct-2024
villagehosting.in team
25 October 2024
A freshly provisioned VPS is already being scanned by bots within minutes of going live. This guide walks through the essential hardening steps every Linux VPS needs in the first hour — in the correct order.
Step 1: Update the system immediately
sudo apt update && sudo apt upgrade -y
sudo apt autoremove -y
Never skip this. Many VPS images are months old at provisioning time and contain known vulnerabilities.
Step 2: Create a non-root user
Running as root is dangerous — any mistake or compromise has full system access. Create a regular user and grant sudo:
adduser yourname
usermod -aG sudo yourname
Switch to this user for the rest of this guide:
su - yourname
Step 3: SSH key authentication
Passwords are not safe for SSH
Bots scan port 22 continuously and attempt thousands of password combinations per minute. A weak password on a fresh VPS can be compromised within hours of provisioning. SSH keys are cryptographically secure and cannot be brute-forced.
Passwords can be brute-forced. SSH keys cannot (with a reasonable key length).
On your local machine, generate a key pair if you don't have one:
ssh-keygen -t ed25519 -C "your-vps-login"
This creates ~/.ssh/id_ed25519 (private key, stays on your machine) and ~/.ssh/id_ed25519.pub (public key, goes to the server).
On the server, add your public key:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys
# Paste the contents of your id_ed25519.pub file
chmod 600 ~/.ssh/authorized_keys
Test the key works by opening a new terminal and connecting: ssh yourname@your-vps-ip. If it logs in without a password prompt, proceed to disable password authentication.
Disable password auth — edit /etc/ssh/sshd_config:
sudo nano /etc/ssh/sshd_config
Find and set:
PasswordAuthentication no
ChallengeResponseAuthentication no
PermitRootLogin no
Restart SSH:
sudo systemctl restart sshd
Step 4: Change the SSH port (optional but effective)
Port 22 is attacked constantly. Moving SSH to a non-standard port reduces log noise and stops the vast majority of automated scans.
In /etc/ssh/sshd_config:
Port 2222
Remember to open this port in your firewall before restarting SSH, or you'll lock yourself out.
Step 5: Configure UFW firewall
UFW (Uncomplicated Firewall) wraps iptables with a simple interface:
sudo apt install ufw -y
# Default: deny incoming, allow outgoing
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH (use your custom port if you changed it)
sudo ufw allow 2222/tcp
# Allow web traffic
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Allow other services as needed:
# sudo ufw allow 3306/tcp # MySQL (only if remote access needed)
# sudo ufw allow 5432/tcp # PostgreSQL (only if remote access needed)
# sudo ufw allow 6379/tcp # Redis (NEVER expose to internet)
# Enable the firewall
sudo ufw enable
sudo ufw status verbose
Important: Never expose database ports (3306, 5432, 6379) to the internet. Use SSH tunneling or private VLAN for database access from other servers.
Step 6: Install and configure fail2ban
fail2ban monitors log files and temporarily bans IPs that show signs of brute-force attacks:
sudo apt install fail2ban -y
Create a local configuration (never edit the main config — it's overwritten on updates):
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8 YOUR.HOME.IP.ADDRESS
[sshd]
enabled = true
port = 2222
logpath = /var/log/auth.log
maxretry = 3
bantime = 24h
[nginx-http-auth]
enabled = true
[nginx-limit-req]
enabled = true
sudo systemctl start fail2ban
sudo systemctl enable fail2ban
# Check status
sudo fail2ban-client status sshd
To check banned IPs: sudo fail2ban-client status sshd
To unban an IP: sudo fail2ban-client set sshd unbanip 1.2.3.4
Step 7: Automatic security updates
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
Edit /etc/apt/apt.conf.d/50unattended-upgrades to configure email notifications:
Unattended-Upgrade::Mail "your@email.com";
Unattended-Upgrade::MailOnlyOnError "false";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
Security patches are applied automatically. OS upgrades still require manual review.
Step 8: Disable unused services
Check what's running:
sudo systemctl list-units --type=service --state=running
Common services to disable on a VPS if you don't need them:
sudo systemctl disable --now postfix # Mail server (unless you're running one)
sudo systemctl disable --now cups # Printing
sudo systemctl disable --now avahi-daemon # mDNS/Bonjour discovery
Step 9: Set up login notifications (optional)
Know immediately when someone logs into your server. Add to /etc/profile or /etc/bash.bashrc:
if [ -n "$SSH_CLIENT" ]; then
LOGINIP=$(echo $SSH_CLIENT | awk '{print $1}')
curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \
-d "chat_id=$TELEGRAM_CHAT_ID" \
-d "text=VPS Login: $USER from $LOGINIP at $(date)"
fi
Or use the simpler approach with email via sendmail if installed.
Step 10: Check open ports
Verify only the ports you intended are open:
sudo ss -tuln
# or
sudo netstat -tuln
Any port you don't recognise is worth investigating.
Security checklist summary
| Item | Command to verify |
|---|---|
| Password auth disabled | sshd -T | grep passwordauth |
| Root login disabled | sshd -T | grep permitrootlogin |
| UFW active | sudo ufw status |
| fail2ban running | sudo systemctl status fail2ban |
| Auto-updates active | sudo systemctl status unattended-upgrades |
| No unexpected open ports | sudo ss -tuln |
These steps cover 95% of the attack surface for a standard VPS. The remaining 5% (application-level security, WAF, intrusion detection) depends on what you're running on the server.