Every server connected to the internet is under attack. Not hypothetically. Right now. Automated bots scan the entire IPv4 address space in under 45 minutes, probing for default credentials, unpatched services, and misconfigured firewalls. The median time from a new server going live to receiving its first SSH brute-force attempt is under 90 seconds.

The good news: the vast majority of successful breaches exploit preventable misconfigurations. A methodical hardening process, applied once and audited quarterly, eliminates most of the attack surface that matters.

This guide covers the critical sections of a proper Linux server hardening process with real commands you can run today. It's written for Ubuntu 22.04/24.04 and Debian 12, though most of it applies to any modern Linux distribution. We'll go deep on the areas that matter most: initial user setup, SSH hardening, firewall configuration, automatic updates, and logging.

⚠️ Before you start: Always have out-of-band access (console, IPMI, or VNC) before changing SSH or firewall settings. Locking yourself out of a remote server is a real risk, and one bad sshd_config change can make it permanent.

01 Initial Server Setup

The first five minutes after provisioning a server determine its security baseline. Cloud images ship weeks or months behind on patches, with root login enabled and no firewall. Fix that immediately.

Create a non-root admin user

Running everything as root is the single most dangerous habit in server administration. One mistyped command, one compromised process, and the entire system is gone. Create a dedicated admin user and use sudo for privileged operations.

adduser deployer
usermod -aG sudo deployer

Set up SSH key authentication

Password authentication is dead. SSH keys use Ed25519 elliptic-curve cryptography. They're faster, shorter, and immune to brute-force attacks. Generate a key pair on your local machine and push the public key to the server:

# On your LOCAL machine:
ssh-keygen -t ed25519 -C "[email protected]"
ssh-copy-id deployer@your-server-ip

Verify you can log in with the key before proceeding. The next step will disable password login entirely.

Disable root login and password authentication

The root account is the first thing every automated scanner targets. Disable it over SSH, along with password auth:

# In /etc/ssh/sshd_config:
PermitRootLogin no
PasswordAuthentication no

# Test config before restarting:
sshd -t
systemctl restart sshd

Keep your current SSH session open while you test login from a second terminal. If something is wrong, you still have access to fix it.

Set hostname and time synchronization

Accurate time is critical for log correlation, TLS certificate validation, and distributed systems. UTC is the only sane choice for servers:

hostnamectl set-hostname prod-web-01
timedatectl set-timezone UTC
apt install -y chrony
systemctl enable chrony

02 Firewall Configuration

Without a firewall, every listening port on your server is accessible to the entire internet. UFW (Uncomplicated Firewall) is the standard on Ubuntu and Debian. It wraps iptables/nftables in a sane interface.

apt install -y ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw --force enable

That's four commands. Your server now drops all incoming traffic except SSH. Add only the ports your application actually needs:

# Web server
ufw allow 80/tcp
ufw allow 443/tcp

# Check your rules
ufw status verbose

Rate-limit SSH

UFW has a built-in rate limiter that throttles connections from IPs that attempt more than 6 connections in 30 seconds:

ufw limit ssh/tcp

Lock down database ports

Databases should never be exposed to the public internet. Bind them to localhost or restrict access to specific IPs:

# Allow MySQL from a specific internal IP only
ufw allow from 10.0.0.5 to any port 3306

# Or just block it entirely (rely on localhost binding)
ufw deny 3306

Enable logging so you can see what's being blocked:

ufw logging on

Pro tip: If your hosting provider offers a cloud firewall (AWS Security Groups, DigitalOcean Firewall, Hetzner Firewall), use it in addition to UFW. Defense in depth means an attacker needs to bypass two layers, not one.

03 SSH Hardening

SSH is typically the only remote access point to your server. The defaults are permissive by design. Tighten them.

Install Fail2ban

Fail2ban monitors your auth logs and automatically bans IPs after repeated failed login attempts. It's one of the highest-value, lowest-effort security tools available:

apt install -y fail2ban

cat > /etc/fail2ban/jail.local <<'EOF'
[sshd]
enabled = true
port = ssh
maxretry = 3
bantime = 3600
findtime = 600
EOF

systemctl enable --now fail2ban

This bans any IP for one hour after 3 failed attempts within 10 minutes. Adjust bantime upward for production servers — 24 hours (86400) is reasonable.

Restrict who can log in

The AllowUsers directive creates an explicit whitelist. Even if a new user account is created (by an attacker or a misconfigured script), they can't SSH in unless they're on the list:

# In /etc/ssh/sshd_config:
AllowUsers deployer
MaxAuthTries 3
LoginGraceTime 30

Use strong ciphers only

Disable legacy cryptographic algorithms. Modern SSH should use only post-quantum and curve25519 key exchange with authenticated encryption:

KexAlgorithms [email protected],curve25519-sha256
Ciphers [email protected],[email protected]
MACs [email protected],[email protected]

Disable unnecessary features

X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
ClientAliveInterval 300
ClientAliveCountMax 2

The ClientAlive settings disconnect idle sessions after 10 minutes of inactivity — useful for preventing hijacking of abandoned sessions.

Always run sshd -t to validate your config before restarting the SSH daemon. A syntax error will lock you out.

Want the full 150+ item checklist?

This post covers the essentials. The complete Server Hardening Checklist Bundle includes 15 security domains, an interactive automation script, and a printable quick-reference card you can pin next to your terminal.

Get the full checklist — $19

04 System Updates & Patching

Most breaches exploit known vulnerabilities with available patches. The patch was published. The CVE was assigned. The admin just didn't update.

Update immediately after provisioning

apt update && apt upgrade -y

Enable automatic security updates

Unattended upgrades apply security patches automatically. Zero-days get patched within hours. If your server doesn't auto-update, you're vulnerable until your next manual session:

apt install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

For kernel updates that require a reboot, configure an automatic reboot during your maintenance window:

# In /etc/apt/apt.conf.d/50unattended-upgrades:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

Remove unnecessary packages

Every installed package is potential attack surface. If you don't need it, remove it:

apt autoremove -y

05 Network Security

Kernel-level network parameters control how your server handles IP traffic at the lowest level. These sysctl settings harden the TCP/IP stack against common attacks:

# Create /etc/sysctl.d/99-hardening.conf:

# Disable ICMP redirects (prevent MITM rerouting)
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0

# Enable SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048

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

# Enable reverse path filtering (drop spoofed packets)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Log martian packets
net.ipv4.conf.all.log_martians = 1

# Disable IP forwarding (unless you're a router/VPN)
net.ipv4.ip_forward = 0

# Restrict kernel pointer and dmesg access
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2

Apply the changes:

sysctl --system

If you're not using IPv6, disable it entirely to cut the network attack surface in half:

net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1

06 File System Security

Proper file permissions limit the blast radius of a compromise. Even if an attacker gets a foothold, they shouldn't be able to read credentials or escalate privileges through misconfigured files.

Lock down sensitive files

chmod 600 /etc/shadow
chmod 600 /etc/gshadow
chmod 644 /etc/passwd
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Restrict /tmp

The /tmp directory is world-writable and a favorite staging area for exploits. Mount it with restrictions:

# In /etc/fstab:
tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev 0 0

# Apply immediately:
mount -o remount /tmp

Audit SUID binaries

SUID binaries run with the owner's privileges (usually root). A vulnerable SUID binary equals instant privilege escalation. Audit them regularly:

find / -perm -4000 -type f 2>/dev/null

Review the output. If anything looks unfamiliar, investigate it. Remove SUID from binaries that don't need it:

chmod u-s /path/to/suspicious-binary

07 Logging & Monitoring

You can't defend what you can't see. Comprehensive logging is your forensic safety net, and often the only way you'll know a breach happened.

Install auditd

The Linux audit framework logs security-critical system calls. Monitor changes to authentication files, sudoers, and critical configs:

apt install -y auditd
systemctl enable --now auditd

# Watch authentication files
auditctl -w /etc/passwd -p wa -k identity
auditctl -w /etc/shadow -p wa -k identity
auditctl -w /etc/sudoers -p wa -k sudoers

Monitor auth logs

Check these regularly, or better, set up automated alerts:

# Failed logins
grep "Failed password" /var/log/auth.log | tail -20

# Successful logins
last -20

# Sudo usage
grep "sudo:" /var/log/auth.log | tail -20

Forward logs to a remote server

If an attacker compromises your server, the first thing they do is delete the logs. Remote log forwarding ensures you retain forensic evidence:

# In /etc/rsyslog.d/remote.conf:
*.* @@syslog.example.com:514

systemctl restart rsyslog

Protect logs from tampering

chattr +a /var/log/auth.log
chattr +a /var/log/syslog

The +a flag makes files append-only — even root can't delete or modify existing entries without first removing the flag.

08 Service Hardening

Every running service is a potential entry point. Minimize what's running and isolate what remains.

# List running services
systemctl list-units --type=service --state=running

# Disable common unnecessary services
systemctl disable --now avahi-daemon
systemctl disable --now cups
systemctl disable --now rpcbind

# Check listening ports — every one should be intentional
ss -tulnp

Enable AppArmor

AppArmor confines programs to a limited set of resources using mandatory access control. It's already installed on most Ubuntu systems. Make sure it's enforcing:

aa-status
apt install -y apparmor apparmor-utils
systemctl enable --now apparmor
aa-enforce /etc/apparmor.d/*

Use systemd sandboxing

Systemd can sandbox services with zero application changes. Check any service's security posture:

systemd-analyze security nginx

Then add restrictions to the service unit file:

# [Service]
# ProtectSystem=strict
# ProtectHome=yes
# PrivateTmp=yes
# NoNewPrivileges=yes

What We Didn't Cover

This guide hits the areas that matter most for every server. But a complete hardening process goes further. The full checklist also covers:

Get the Complete Server Hardening Checklist Bundle

150+ action items across 15 security domains. Every item includes the exact command, a priority level, and an explanation of why it matters. Plus an interactive harden.sh automation script and a printable quick-reference card.

Download the bundle — $19

Ongoing Maintenance

Hardening isn't a one-time event. Build these into your routine:

Security is a practice, not a destination. The checklist gives you structure. The discipline is yours.