Introduction
A freshly provisioned Ubuntu server is not secure by default. It ships with a broad attack surface: password-based SSH, no firewall, no intrusion detection, and no automatic patching. Every server we put into production should go through a hardening process before it receives any traffic.
This guide provides a complete, step-by-step hardening checklist for Ubuntu 24.04 LTS. We cover SSH lockdown, firewall configuration, brute-force protection, automatic security updates, kernel hardening, and audit logging. Each section includes the exact commands to run.
1. System Updates
Start by ensuring the system is fully patched:
sudo apt update && sudo apt upgrade -y
sudo apt autoremove -y
2. Create a Non-Root User with SSH Key Access
Never use the root account for day-to-day operations:
adduser deploy
usermod -aG sudo deploy
# Set up SSH key for the new user
mkdir -p /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
# Paste the public key
echo "ssh-ed25519 AAAA... user@workstation" > /home/deploy/.ssh/authorized_keys
chmod 600 /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
3. Harden SSH Configuration
Edit /etc/ssh/sshd_config:
sudo tee /etc/ssh/sshd_config.d/hardening.conf > /dev/null << 'EOF'
Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
MaxAuthTries 3
LoginGraceTime 20
X11Forwarding no
AllowTcpForwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers deploy
EOF
sudo systemctl restart sshd
Key changes:
- Non-standard port reduces drive-by scanning noise
- Root login disabled forces use of sudo
- Password auth disabled eliminates brute-force attacks
AllowUsersrestricts SSH to named accounts only
4. Configure UFW Firewall
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 2222/tcp comment "SSH"
sudo ufw allow 80/tcp comment "HTTP"
sudo ufw allow 443/tcp comment "HTTPS"
sudo ufw enable
sudo ufw status verbose
5. Install and Configure Fail2ban
Fail2ban monitors log files and bans IPs that show malicious behavior:
sudo apt install -y fail2ban
sudo tee /etc/fail2ban/jail.local > /dev/null << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
banaction = ufw
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
EOF
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
6. Enable Unattended Security Upgrades
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Verify the configuration:
sudo tee /etc/apt/apt.conf.d/50unattended-upgrades > /dev/null << 'EOF'
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
};
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Mail "admin@example.com";
EOF
7. Kernel Hardening with sysctl
sudo tee /etc/sysctl.d/99-hardening.conf > /dev/null << 'EOF'
# Disable IP forwarding
net.ipv4.ip_forward = 0
# Disable ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# Enable SYN flood protection
net.ipv4.tcp_syncookies = 1
# Disable source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
# Log Martian packets
net.ipv4.conf.all.log_martians = 1
# Restrict kernel pointer exposure
kernel.kptr_restrict = 2
# Restrict dmesg access
kernel.dmesg_restrict = 1
# Restrict unprivileged BPF
kernel.unprivileged_bpf_disabled = 1
EOF
sudo sysctl --system
8. Enable Audit Logging with auditd
sudo apt install -y auditd audispd-plugins
# Watch for changes to critical files
sudo tee /etc/audit/rules.d/hardening.rules > /dev/null << 'EOF'
# Monitor SSH config changes
-w /etc/ssh/sshd_config -p wa -k sshd_config
# Monitor user/group changes
-w /etc/passwd -p wa -k passwd_changes
-w /etc/shadow -p wa -k shadow_changes
-w /etc/group -p wa -k group_changes
# Monitor sudo usage
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers_d
# Monitor cron
-w /etc/crontab -p wa -k cron
-w /var/spool/cron/ -p wa -k cron
# Log all commands run by root
-a always,exit -F arch=b64 -F euid=0 -S execve -k root_commands
EOF
sudo systemctl enable --now auditd
sudo auditctl -l
9. Disable Unnecessary Services
# List all running services
systemctl list-units --type=service --state=running
# Disable services we do not need
sudo systemctl disable --now snapd
sudo systemctl disable --now cups
sudo systemctl disable --now avahi-daemon
10. CIS Benchmark Quick Checks
The Center for Internet Security publishes comprehensive benchmarks for Ubuntu. Here are the most impactful items:
# Verify filesystem permissions
stat -c "%a %n" /etc/passwd # Should be 644
stat -c "%a %n" /etc/shadow # Should be 640
stat -c "%a %n" /etc/group # Should be 644
# Ensure no world-writable files exist in system dirs
find /usr -xdev -type f -perm -0002 -ls
# Check for empty password fields
sudo awk -F: '($2 == "") { print $1 }' /etc/shadow
# Ensure root is the only UID 0 account
awk -F: '($3 == 0) { print $1 }' /etc/passwd
Conclusion
Server hardening is not a one-time task. We should revisit this checklist after every major system upgrade and audit our configurations regularly. Automating these checks with tools like Lynis or OpenSCAP ensures that configuration drift does not silently undo our hardening work. A hardened server is the foundation on which everything else depends.