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_configchange 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 -tto 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:
- Web server hardening: security headers, TLS configuration, rate limiting, directory listing prevention for Nginx and Apache
- Database hardening: localhost binding, least-privilege users, encrypted backups, query auditing for MySQL and PostgreSQL
- Container security: non-root containers, read-only filesystems, image scanning, Docker daemon hardening
- Intrusion detection: AIDE file integrity monitoring, rootkit scanning with rkhunter, ClamAV configuration
- Backup & recovery: automated encrypted backups, offsite replication, tested recovery procedures
- Compliance mapping: how these controls map to SOC 2, ISO 27001, PCI DSS, HIPAA, and CIS Benchmarks
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.
Ongoing Maintenance
Hardening isn't a one-time event. Build these into your routine:
- Weekly: Review auth logs and Fail2ban bans. Check for pending updates.
- Monthly: Audit SUID binaries, review firewall rules, check listening ports for anything unexpected.
- Quarterly: Full re-audit against the checklist. Update TLS certificates. Review user accounts and remove unused ones.
- After any incident: Complete audit. Rotate all credentials. Review logs for lateral movement.
Security is a practice, not a destination. The checklist gives you structure. The discipline is yours.