Linux Server Hardening: What I Actually Do on Fresh VPS Installs

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.

Scroll to Top