7 min read

Ubuntu Server Security: The Pragmatic Linux Hardening Checklist

Ubuntu Server Security: The Pragmatic Linux Hardening Checklist

Within sixty seconds of provisioning a fresh virtual machine on AWS, DigitalOcean, or Hetzner and attaching a public IPv4 address, automated botnets and scanning crawlers begin probing port 22. These bots execute continuous dictionary attacks against root, test known default passwords, and probe for outdated software vulnerabilities.

While Ubuntu Server ships with stable defaults, an unhardened installation leaves dangerous attack vectors open: password authentication enabled on SSH, unrestricted open network ports, missing security patch automation, and zero brute-force rate limiting.

Security is not a theoretical exercise—it is an operational discipline. In this guide, we walk through an opinionated, production-tested checklist to harden a fresh Ubuntu 24.04 / 22.04 LTS server before deploying application workloads.

Part of a Series

Modern Linux & Terminal Mastery Series

Part 3 of 4

Featured Ubuntu Security
Free Developer Tool: Linux Chmod Permissions Calculator

Managing user, group, and world file permissions on your Linux server? Use our visual Linux Chmod Permissions Calculator to generate exact octal and symbolic permission flags with full umask support.


1. Non-Root User Provisioning & Privilege Separation

Never run production applications or interactive terminal sessions directly as root. A typographical mistake in a Bash command or an exploit in an application dependency can execute with unrestricted kernel capabilities.

Create a Dedicated Sudo User

Log in as root and create a dedicated administrative user:

# Add a new administrative user
adduser deployer

# Grant administrative sudo privileges
usermod -aG sudo deployer

# Copy your local SSH public key to the new user
mkdir -p /home/deployer/.ssh
cp /root/.ssh/authorized_keys /home/deployer/.ssh/
chown -R deployer:deployer /home/deployer/.ssh
chmod 700 /home/deployer/.ssh
chmod 600 /home/deployer/.ssh/authorized_keys

Advertisement

2. Hardening OpenSSH (sshd_config)

The SSH daemon is your server's primary front door. We must restrict access exclusively to modern public-key cryptography and disable password authentication entirely.

SSH Hardening

Edit /etc/ssh/sshd_config.d/99-hardened.conf (using drop-in configuration files prevents package upgrades from overwriting your custom rules):

# /etc/ssh/sshd_config.d/99-hardened.conf

# Completely disable root login over SSH
PermitRootLogin no

# Disable password authentication (Keys required)
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no

# Restrict authentication attempts before dropping connection
MaxAuthTries 3
MaxSessions 2

# Modern cryptographic ciphers and key exchanges (Disables legacy RSA/SHA1)
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com

# Disable unused forwarding capabilities to prevent proxy tunneling
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no

Validate your configuration syntax before restarting the daemon:

# Test sshd configuration syntax
sudo sshd -t

# Restart SSH service
sudo systemctl restart ssh
Crucial Sanity Check

Do not close your existing terminal session yet! Open a new terminal tab and verify that you can successfully connect via ssh deployer@your_server_ip using your SSH key.


3. Firewall Segmentation with UFW (Uncomplicated Firewall)

By default, Ubuntu allows all inbound and outbound network connections. We must enforce a Default Deny ingress policy, explicitly opening only the ports your workload requires.

UFW Firewall
# Reset UFW to default clean state
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH with rate limiting (Blocks IPs making >6 connections in 30s)
sudo ufw limit 22/tcp comment 'SSH Rate Limited'

# Open standard web traffic ports
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'

# Enable the firewall
sudo ufw enable

# Verify active status
sudo ufw status verbose

If your application database (e.g., PostgreSQL on port 5432) must be accessed remotely, never open port 5432 to the entire internet. Restrict access strictly to your internal private subnet or VPN IP:

# Restrict PostgreSQL to internal VPN or backend IP
sudo ufw allow from 10.8.0.5 to any port 5432 proto tcp comment 'Postgres VPN Only'

4. Automated Brute-Force Defense with Fail2ban

Even with password authentication disabled, botnets repeatedly spamming SSH handshakes consume CPU cycles and clutter system log files.

Fail2ban monitors log files (/var/log/auth.log) for repeated authentication failures and dynamically injects temporary iptables drop rules to block offending IP addresses.

# Install Fail2ban
sudo apt-get update && sudo apt-get install -y fail2ban

# Create local configuration override
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Configure /etc/fail2ban/jail.local:

[DEFAULT]
# Ban IP for 1 hour after 5 failures in 10 minutes
bantime = 1h
findtime = 10m
maxretry = 5
banaction = ufw

[sshd]
enabled = true
port = 22
mode = aggressive

Restart and verify Fail2ban status:

sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

Advertisement

5. Automated Security Patching: unattended-upgrades

Zero-day vulnerabilities in OpenSSL, the Linux kernel, or standard system libraries are discovered regularly. Relying on manual apt upgrade commands guarantees your server will run vulnerable packages between maintenance windows.

Enable automatic security patching:

sudo apt-get install -y unattended-upgrades update-notifier-common
sudo dpkg-reconfigure --priority=low unattended-upgrades

Edit /etc/apt/apt.conf.d/50unattended-upgrades:

// Automatically install critical security updates
Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
};

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

// Automatically reboot if a kernel update requires it (at 03:00 AM)
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";

6. Kernel & Network Parameter Hardening (sysctl)

Harden the Linux network stack against SYN floods, IP spoofing, and man-in-the-middle packet redirects by adding these parameters to /etc/sysctl.d/99-security.conf:

# /etc/sysctl.d/99-security.conf

# Ignore ICMP echo broadcasts (Prevents Smurf attacks)
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Protect against SYN flood denial of service
net.ipv4.tcp_syncookies = 1

# Disable ICMP redirect acceptance (Prevents routing table poisoning)
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0

# Enable IP spoofing protection (Reverse Path Filtering)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

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

# Restrict dmesg kernel log access to root
kernel.dmesg_restrict = 1

Apply sysctl changes immediately without rebooting:

sudo sysctl --system

Linux Hardening Verification Matrix

Hardening StepCommand to VerifyExpected Output
Root SSH Disabledssh root@<IP>Permission denied (publickey)
Password Auth Disabledssh -o PubkeyAuthentication=no deployer@<IP>Permission denied (publickey)
Firewall Activesudo ufw statusStatus: active (Deny incoming)
Fail2ban Runningsudo fail2ban-client pingServer replied: pong
Auto Upgrades Configuredsystemctl is-active unattended-upgradesactive

Frequently Asked Questions

Should I change the default SSH port from 22 to a random port?

Moving SSH to a non-standard port (e.g., port 2222) reduces automated bot noise in /var/log/auth.log, but it is security through obscurity. It does not protect against targeted port scans. Enforcing SSH keys, disabling passwords, and running Fail2ban provides genuine security regardless of the port number.

How do I safely test my firewall without locking myself out?

When configuring UFW on a remote server, execute a scheduled cron job or background sleep command to reset UFW if you lose connection: sudo sh -c "sleep 300 && ufw disable" & If your new rules lock you out, wait 5 minutes for the timer to disable UFW and restore access. Once you confirm connectivity works, cancel the background task.

How often should SSH keys be rotated?

Modern Ed25519 keys do not expire mathematically, but organizations should rotate keys whenever a team member departs or once per year as part of standard security audits. Consider using OpenSSH Certificate Authorities or hardware security keys (YubiKeys with FIDO2) for enterprise fleets.


You Might Also Like

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement