Linux 5 min read

Linux Server Hardening: What to Do After the First Boot

A practical step-by-step guide to hardening a Linux server — SSH, firewall, fail2ban, automatic updates, auditd, and AppArmor for production-ready security.

linuxsecurityhardeningsshfirewallfail2banserver

Installing a Linux server is the easy part. Hardening it — that’s where most people either overcomplicate things or skip critical steps entirely.

This guide covers the practical hardening steps I apply to every new server, whether it’s a VPS, a Proxmox VM, or a bare-metal homelab node. These are ordered from most impactful to least, so even if you only do the first few, you’re in a better place than most.

1. SSH Hardening

SSH is your primary attack surface. A default installation on port 22 with password authentication is a matter of time before it’s in someone’s logs.

Disable Password Authentication

# Only run this AFTER your SSH key is confirmed working
sudo sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config

Restrict Root Login

sudo sed -i 's/^#PermitRootLogin prohibit-password/PermitRootLogin no/' /etc/ssh/sshd_config
sudo sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config

Additional SSH Hardening Options

Add or ensure these settings in /etc/ssh/sshd_config:

# /etc/ssh/sshd_config additions
MaxAuthTries 3
MaxSessions 3
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers your-user     # Only allow specific users
Protocol 2                # Disable legacy protocol

Apply changes:

sudo systemctl restart sshd

Verify

# Should return an error (no password prompt) if password auth is disabled
ssh -o PreferredAuthentications=password your-user@localhost

2. Firewall: ufw

ufw (Uncomplicated Firewall) is the simplest way to lock down a server. Default deny incoming, allow only what you need:

sudo ufw default deny incoming
sudo ufw default allow outgoing

# SSH — change to your port if you moved it
sudo ufw allow ssh

# Web services (if applicable)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Enable
sudo ufw --force enable
sudo ufw status verbose

For more complex rules, nftables is the modern replacement for iptables, but ufw covers 90% of homelab use cases.

Alternative: nftables Quick Reference

sudo nft add table inet filter
sudo nft add chain inet filter input '{ type filter hook input priority 0; policy drop; }'
sudo nft add rule inet filter input ct state established,related accept
sudo nft add rule inet filter input iif lo accept
sudo nft add rule inet filter input tcp dport 22 accept

3. Fail2ban

Fail2ban monitors logs and temporarily bans IPs with repeated failed login attempts:

sudo apt install fail2ban

Create a local config file instead of editing the defaults:

# /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
banaction = ufw

[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd

Fail2ban is a force multiplier. A single VPS with SSH on the default port can accumulate hundreds of failed login attempts daily. Fail2ban drops that to zero after the first few.

4. Automatic Security Updates

Unattended upgrades keep your server patched against known CVEs without manual intervention:

sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades

Review and adjust the configuration:

sudo nano /etc/apt/apt.conf.d/50unattended-upgrades

Key settings to verify:

// Auto-fix broken packages
Unattended-Upgrade::AutoFixInterruptedDependencies "true";

// Send email alerts (configure postfix or msmtp first)
Unattended-Upgrade::Mail "[email protected]";

// Remove unused kernel packages
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";

// Remove unused dependencies
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";

// Reboot if needed (set to false if you prefer manual reboots)
Unattended-Upgrade::Automatic-Reboot "false";

5. Auditd for System Auditing

auditd tracks system events — who accessed what, when, and from where:

sudo apt install auditd

Add key monitoring rules:

# Monitor /etc/passwd and /etc/shadow changes
sudo auditctl -w /etc/passwd -p wa -k passwd-changes
sudo auditctl -w /etc/shadow -p wa -k shadow-changes

# Monitor SSH configuration changes
sudo auditctl -w /etc/ssh/sshd_config -p wa -k sshd-config

# Monitor su and sudo usage
sudo auditctl -w /usr/bin/su -p x -k su-exec
sudo auditctl -w /usr/bin/sudo -p x -k sudo-exec

Make rules persistent:

sudo nano /etc/audit/rules.d/custom.rules

Contents:

-w /etc/passwd -p wa -k passwd-changes
-w /etc/shadow -p wa -k shadow-changes
-w /etc/ssh/sshd_config -p wa -k sshd-config
-w /usr/bin/su -p x -k su-exec
-w /usr/bin/sudo -p x -k sudo-exec
sudo systemctl restart auditd

Query audit logs:

# Recent sudo executions
sudo ausearch -k sudo-exec --start recent

# Live monitoring
sudo tail -f /var/log/audit/audit.log

6. AppArmor

AppArmor provides mandatory access control — restricting what processes can do even if they’re compromised:

# Check status
sudo aa-status

# Install apparmor-utils for management
sudo apt install apparmor-utils

# Enforce a profile
sudo aa-enforce /usr/bin/man  # Example
sudo aa-complain /usr/bin/foo  # Log violations but don't enforce

Most Ubuntu packages ship with AppArmor profiles already. Ensure they’re in enforce mode:

sudo aa-status | head -20

Look for profiles in complain mode and consider moving them to enforce after testing.

7. Kernel Hardening (sysctl)

These sysctl settings reduce kernel-level attack surface:

# /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

# Disable source packet routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0

# Ignore send redirects
net.ipv4.conf.all.send_redirects = 0

# Enable TCP SYN cookies
net.ipv4.tcp_syncookies = 1

# Reduce connection timeout DoS risk
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_synack_retries = 2
sudo sysctl -p /etc/sysctl.d/99-hardening.conf

8. Regular Verification

Security is a process, not a configuration. Verify periodically:

# Check listening ports
ss -tlnp

# Check failed login attempts
sudo journalctl -u sshd | grep "Failed password" | wc -l

# Check available updates
apt list --upgradable 2>/dev/null | grep -v "^Listing"

# Verify firewall rules
sudo ufw status verbose

# Check AppArmor status
sudo aa-status | head -10

The 80/20 Rule

If you only have 30 minutes for server hardening, do these three things in order:

  1. Disable SSH password authentication — eliminates the most common attack vector
  2. Configure the firewall — default deny incoming, allow only what’s necessary
  3. Enable unattended-upgrades — patched automatically or not at all

Everything else is defense in depth. Worth having, but these three steps eliminate the vast majority of real-world server compromises.


Need help hardening your infrastructure? Contact us for a security review.