Ubuntu Server Security: The Pragmatic Linux Hardening Checklist

Table of Contents
- 1. Non-Root User Provisioning & Privilege Separation
- Create a Dedicated Sudo User
- 2. Hardening OpenSSH (sshd_config)
- 3. Firewall Segmentation with UFW (Uncomplicated Firewall)
- 4. Automated Brute-Force Defense with Fail2ban
- 5. Automated Security Patching: unattended-upgrades
- 6. Kernel & Network Parameter Hardening (sysctl)
- Linux Hardening Verification Matrix
- Frequently Asked Questions
- Should I change the default SSH port from 22 to a random port?
- How do I safely test my firewall without locking myself out?
- How often should SSH keys be rotated?
- You Might Also Like
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.
Modern Linux & Terminal Mastery Series

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
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.

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.

# 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
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 Step | Command to Verify | Expected Output |
|---|---|---|
| Root SSH Disabled | ssh root@<IP> | Permission denied (publickey) |
| Password Auth Disabled | ssh -o PubkeyAuthentication=no deployer@<IP> | Permission denied (publickey) |
| Firewall Active | sudo ufw status | Status: active (Deny incoming) |
| Fail2ban Running | sudo fail2ban-client ping | Server replied: pong |
| Auto Upgrades Configured | systemctl is-active unattended-upgrades | active |
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
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

SSH and SCP: The Two Tools Every Developer Should Actually Understand
A no-fluff guide to SSH and SCP — covering port 22, key-based auth, the SSH config file, secure file transfers, and server hardening tips every developer should know.
Read more
Implementing Zero-Trust Security in Kubernetes: The Complete Production Guide
Practical guide to eliminating flat-network perimeter security in Kubernetes: default-deny NetworkPolicies, SPIFFE/SPIRE workload identity, and strict mTLS.
Read more
GitHub Actions Self-Hosted Runner Security Hardening
Harden self-hosted GitHub Actions runners using Actions Runner Controller (ARC), network isolation, rootless containers, and short-lived OIDC tokens.
Read more