VPS Security Hardening Checklist: 20 Steps to Lock Down a Production Server

Introduction
You just provisioned a fresh VPS. It has a public IP address and a root password. Within minutes, bots will start scanning it. If you deploy an application before locking down the server, you are inviting trouble.
This checklist covers 20 steps to harden a Linux server from a fresh Ubuntu 24.04 install to a production-grade setup. Every step includes the exact commands. No theory, no fluff - just a secure server by the end of this page.
Prerequisites
- Fresh Ubuntu 24.04 (works on 22.04 and Debian 12 with minor adjustments)
- Root access
- A non-root user you will create in Step 2
1. Update the System
apt update && apt upgrade -y
Do this first. Always. Patching known vulnerabilities is step zero of any security effort.
2. Create a Non-Root User
Never SSH as root. Create a user with sudo privileges:
adduser deploy
usermod -aG sudo deploy
Replace deploy with your preferred username.
3. SSH Key-Only Authentication
Disable password authentication. First, copy your public key:
# On your local machine:
ssh-copy-id -i ~/.ssh/id_ed25519 deploy@your-server-ip
Then edit /etc/ssh/sshd_config:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
Test with a NEW terminal window before closing the current session:
ssh deploy@your-server-ip
If it connects, restart SSH:
systemctl restart sshd
4. Change the SSH Port
Move SSH off port 22 to reduce log noise from automated scanners:
# In /etc/ssh/sshd_config
Port 2222
Update your firewall (step 5) to allow the new port. Restart SSH:
systemctl restart sshd
Connect on the new port before closing your current session:
ssh -p 2222 deploy@your-server-ip
5. Configure UFW Firewall
ufw default deny incoming
ufw default allow outgoing
ufw allow 2222/tcp # SSH (custom port)
ufw allow 80/tcp # HTTP
ufw allow 443/tcp # HTTPS
ufw enable
ufw status verbose
Only open ports your application actually needs. For a web server, ports 80 and 443 plus your SSH port are sufficient.
6. Install and Configure Fail2Ban
Fail2Ban blocks IPs after repeated failed login attempts:
apt install fail2ban -y
Create /etc/fail2ban/jail.local:
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
ignoreip = 127.0.0.1/8 ::1
[sshd]
enabled = true
port = 2222
logpath = /var/log/auth.log
[nginx-http-auth]
enabled = true
[nginx-botsearch]
enabled = true
Start and enable:
systemctl enable fail2ban --now
fail2ban-client status sshd
7. Upgrade to CrowdSec (Recommended)
CrowdSec is the modern replacement for Fail2Ban. It uses community-shared blocklists and behavioral analysis. Install alongside or as a replacement:
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | bash
apt install crowdsec crowdsec-firewall-bouncer-iptables -y
systemctl enable crowdsec --now
CrowdSec automatically detects and blocks SSH brute-force, HTTP attacks, and more. The free tier includes community blocklists updated hourly.
8. Disable Root Login and Unused Services
passwd -l root # Lock root password
systemctl disable --now rpcbind 2>/dev/null
systemctl disable --now avahi-daemon 2>/dev/null
systemctl disable --now cups 2>/dev/null
List all running services and disable anything you do not need:
systemctl list-units --type=service --state=running
9. Enable Automatic Security Updates
apt install unattended-upgrades -y
dpkg-reconfigure -plow unattended-upgrades
Select "Yes" when prompted. This automatically installs security patches daily. Edit /etc/apt/apt.conf.d/50unattended-upgrades to enable automatic reboots if needed:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "02:00";
10. Configure Kernel Hardening via sysctl
Create /etc/sysctl.d/99-hardening.conf:
# IP spoofing protection
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# Ignore send redirects
net.ipv4.conf.all.send_redirects = 0
# Disable source packet routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 2
# Disable IP forwarding (unless you need routing)
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0
# Log Martian packets
net.ipv4.conf.all.log_martians = 1
Apply:
sysctl -p /etc/sysctl.d/99-hardening.conf
11. Secure Shared Memory
Add to /etc/fstab:
tmpfs /run/shm tmpfs defaults,noexec,nosuid 0 0
Mount it:
mount -o remount /run/shm
12. Set Up AppArmor
AppArmor is enabled by default on Ubuntu. Verify:
aa-status
Create custom profiles for your application if needed. At minimum, ensure the default profiles are loaded and enforcing.
13. Install and Configure auditd
Auditd logs system calls for forensic analysis:
apt install auditd -y
systemctl enable auditd --now
Add rules to /etc/audit/rules.d/audit.rules:
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
-w /var/log/auth.log -p wa -k authlog
-w /etc/ssh/sshd_config -p wa -k sshd_config
Restart auditd:
systemctl restart auditd
14. Disable IPv6 (If Not Used)
If your application does not need IPv6, disable it:
# In /etc/sysctl.conf
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
Apply:
sysctl -p
15. Set Up a Firewall for Outbound Traffic
Restrict outbound connections to only what your server needs:
ufw default deny outgoing
ufw allow out 53 # DNS
ufw allow out 80/tcp # HTTP (apt updates)
ufw allow out 443/tcp # HTTPS (API calls, webhooks)
ufw allow out 123/udp # NTP
ufw allow out 2222/tcp # Your custom SSH port for outbound SSH
Add application-specific outbound rules (e.g., SMTP port 587 for email). Test thoroughly before deploying - outbound restrictions break package updates and external API calls.
16. Harden Nginx/Apache
If running a web server, add security headers. For Nginx, add to your server block:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# HSTS (only with valid SSL)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Hide version information:
server_tokens off;
17. Set Up Monitoring
Install Node Exporter for Prometheus monitoring:
apt install prometheus-node-exporter -y
systemctl enable prometheus-node-exporter --now
Or use Netdata for a quick all-in-one dashboard:
curl https://get.netdata.cloud/kickstart.sh > /tmp/netdata-kickstart.sh
sh /tmp/netdata-kickstart.sh --stable-channel --disable-telemetry
Monitor CPU, memory, disk, network, and process activity. Alert on anomalies.
18. Enable Process Accounting
Process accounting logs every command executed:
apt install acct -y
systemctl enable acct --now
View command history:
lastcomm
sa
19. Set File Integrity Monitoring
Install AIDE (Advanced Intrusion Detection Environment):
apt install aide -y
aideinit
mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
Run daily checks via cron:
# /etc/cron.daily/aide-check
#!/bin/bash
/usr/bin/aide --check | mail -s "AIDE Report $(hostname)" [email protected]
AIDE alerts you when critical system files change unexpectedly.
20. Regular Maintenance Checklist
Set up a monthly cron job:
#!/bin/bash
apt update && apt upgrade -y
rkhunter --check --skip-keypress
chkrootkit
aide --check
fail2ban-client status
systemctl list-units --state=failed
df -h
free -h
Save this as /root/bin/monthly-check.sh and run it on the first of every month.
How ServerGurus Helps
Every ServerGurus VPS ships with steps 1-10 pre-configured: SSH key-only auth, UFW with custom SSH port, Fail2Ban/CrowdSec, unattended upgrades, and kernel hardening. Our managed hosting plans add 24/7 monitoring, intrusion detection, and incident response. You focus on your application, we handle the rest.
For a VPS that is secure from the moment you log in, check our plans.