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.
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:
- Disable SSH password authentication — eliminates the most common attack vector
- Configure the firewall — default deny incoming, allow only what’s necessary
- 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.