Every time I set up a new VPS, I go through the same checklist. Some of it is obvious, some of it I learned the hard way, and some of it is just habit at this point. This isn’t a comprehensive security audit — it’s the practical stuff I do within the first hour of having root access to a new machine.
Start with Updates
Before anything else:
apt update && apt upgrade -y # Debian/Ubuntu
# or
dnf upgrade -y # RHEL/CentOS/Fedora
Fresh images from cloud providers often have packages weeks or months out of date. Get current before doing anything else. Reboot if the kernel was updated.
Create a Non-Root User
Running everything as root is a bad habit. Create a user with sudo access:
useradd -m -s /bin/bash deploy
usermod -aG sudo deploy # Debian/Ubuntu
# or
usermod -aG wheel deploy # RHEL/CentOS
passwd deploy
Copy your SSH keys to the new user (I cover this more below):
mkdir -p /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
SSH Configuration
This is the most important part. Edit /etc/ssh/sshd_config:
# Disable root login
PermitRootLogin no
# Disable password authentication (use keys only)
PasswordAuthentication no
ChallengeResponseAuthentication no
# Disable empty passwords
PermitEmptyPasswords no
# Optional: change the default port
# Port 2222
# Limit login attempts
MaxAuthTries 3
MaxSessions 5
# Use modern algorithms
Protocol 2
# Optional: restrict to specific users
AllowUsers deploy
# Idle session timeout (30 minutes)
ClientAliveInterval 1800
ClientAliveCountMax 0
Restart SSH — but first open a second terminal and verify you can still log in before closing your current session:
sshd -t # test config syntax
systemctl restart sshd
Seriously, test from a second terminal before closing the current one. Getting locked out of a fresh VPS is embarrassing.
Firewall Setup with UFW
UFW is the friendly frontend to iptables. Simple and effective for most cases.
apt install ufw
# Default policies
ufw default deny incoming
ufw default allow outgoing
# Allow SSH first (before enabling!)
ufw allow 22/tcp # or whatever port you chose
# Allow web traffic
ufw allow 80/tcp
ufw allow 443/tcp
# Enable
ufw enable
ufw status verbose
The order matters a little here — make sure you allow SSH before enabling the firewall, or you’ll lock yourself out. I’ve done this once. Once was enough.
If you changed SSH to a non-standard port:
ufw allow 2222/tcp
Automatic Security Updates
On Debian/Ubuntu, unattended-upgrades handles automatic security patches:
apt install unattended-upgrades
dpkg-reconfigure --priority=low unattended-upgrades
Or configure manually in /etc/apt/apt.conf.d/50unattended-upgrades. I enable security updates automatically, but leave regular package updates for manual review — I’ve been bitten by automatic upgrades breaking application dependencies.
Fail2ban
Fail2ban monitors log files and temporarily bans IPs that show malicious behavior — repeated failed login attempts being the most common trigger.
apt install fail2ban
Create a local configuration (don’t edit the main jail.conf — it gets overwritten on upgrades):
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Edit /etc/fail2ban/jail.local:
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
backend = auto
[sshd]
enabled = true port = ssh logpath = %(sshd_log)s maxretry = 3
systemctl enable fail2ban
systemctl start fail2ban
fail2ban-client status sshd
To unban an IP manually:
fail2ban-client set sshd unbanip 1.2.3.4
Secure Shared Memory
Add this to /etc/fstab:
tmpfs /run/shm tmpfs defaults,noexec,nosuid 0 0
This prevents processes from using shared memory in ways they shouldn’t. Remount without rebooting:
mount -o remount /run/shm
Kernel Parameters (sysctl)
A few network-level settings that improve security. Add to /etc/sysctl.d/99-hardening.conf:
# Disable IP source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# Disable ICMP redirect acceptance
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
# Log suspicious packets
net.ipv4.conf.all.log_martians = 1
# Disable IP forwarding (unless you need it for routing/VPN)
net.ipv4.ip_forward = 0
# Enable SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_syn_retries = 2
# Protect against TIME_WAIT assassination
net.ipv4.tcp_rfc1337 = 1
# Disable ICMP broadcasts
net.ipv4.icmp_echo_ignore_broadcasts = 1
Apply:
sysctl -p /etc/sysctl.d/99-hardening.conf
Note: if you’re running a VPN like WireGuard, you’ll need net.ipv4.ip_forward = 1 and net.ipv4.conf.all.send_redirects = 0.
Remove Unnecessary Services
See what’s listening:
ss -tlnp
# or
netstat -tlnp
Disable anything you don’t need:
systemctl disable avahi-daemon
systemctl stop avahi-daemon
systemctl disable cups
systemctl stop cups
Common ones to check: avahi-daemon (mDNS), cups (printing), bluetooth, rpcbind (NFS-related), nfs-server (if you don’t use NFS).
Fewer open ports = smaller attack surface. Obvious, but worth actually checking rather than assuming.
Audit Installed Packages
On a fresh VPS, there are usually packages installed that you don’t need:
dpkg --get-selections | grep -v deinstall
I typically remove things like telnet, rsh, nis (NIS/NIS+ networking). Old, insecure protocols that have no business being on a modern server.
apt remove telnet rsh-client nis
Set Up Log Monitoring
At minimum, keep an eye on auth logs:
tail -f /var/log/auth.log # Debian/Ubuntu
tail -f /var/log/secure # RHEL/CentOS
I also set up logwatch or send logs to a central syslog server on anything that matters. For lightweight monitoring, logwatch sends daily summaries:
apt install logwatch
Configure in /etc/logwatch/conf/logwatch.conf — set Output = mail and MailTo to your email address.
File Permissions
Check for world-writable files and unusual SUID binaries:
find / -perm -002 -not -path "/proc/*" -not -path "/sys/*" 2>/dev/null
find / -perm -4000 -type f 2>/dev/null # SUID binaries
find / -perm -2000 -type f 2>/dev/null # SGID binaries
SUID binaries are legitimate but worth knowing what’s there. Any world-writable files outside of /tmp and /dev deserve investigation.
SSH Key Management
If multiple people have access, I use separate keys per person. Never share private keys. When someone leaves, remove their key from authorized_keys — this is something that gets forgotten more often than it should.
For a more detailed dive into SSH key setup and security practices, the SSH key authentication guide covers that topic specifically.
A Note on Security Tools
A few tools worth installing for periodic audits:
apt install lynis rkhunter chkrootkit
lynis audit system # comprehensive audit
rkhunter --check # rootkit check
Lynis especially is worth running after initial setup — it gives a scored report and specific recommendations. I run it a few times a year on production servers.
None of this is a substitute for keeping software updated and monitoring logs regularly. Security is ongoing, not a one-time setup.