Linux Server Hardening Checklist — 20 Steps to Secure Your Server
You just deployed a server on the internet — here are the 20 things you must do before going to sleep. Every minute an unhardened server is exposed, automated scanners are probing it. Shodan indexes new IPs within hours. This isn't theoretical — this is Tuesday.
Step 1: Update Everything
Before anything else, patch the system. Known vulnerabilities are the easiest attack vector.
# Debian/Ubuntu
sudo apt update && sudo apt upgrade -y && sudo apt autoremove -y
# RHEL/AlmaLinux/Rocky
sudo dnf update -y && sudo dnf autoremove -y
# Check for kernel updates requiring reboot
needs-restarting -r 2>/dev/null || echo "Check if reboot needed"
Step 2: Create a Non-Root User with sudo
Never operate as root. Create a dedicated admin account.
# Create user with home directory and bash shell
useradd -m -s /bin/bash deployer
echo "deployer ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/deployer
chmod 440 /etc/sudoers.d/deployer
# Set up SSH key authentication for the new user
mkdir -p /home/deployer/.ssh
cp ~/.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
Step 3: Harden SSH Configuration
SSH is the primary attack surface. Lock it down aggressively.
# Back up original config
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
# Apply hardened settings
sudo tee /etc/ssh/sshd_config.d/hardened.conf << 'EOF'
# Authentication
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
MaxAuthTries 3
MaxSessions 3
# Network
Port 22
AddressFamily inet
ListenAddress 0.0.0.0
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
# Security
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
PermitTunnel no
PermitUserEnvironment no
# Restrict to specific users
AllowUsers deployer
# Use strong crypto only
KexAlgorithms sway25519-sha256,curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
EOF
# Validate config before restarting
sudo sshd -t && sudo systemctl restart sshd
Step 4: Configure a Firewall
Only allow what's explicitly needed. Deny everything else.
# Using ufw (Ubuntu/Debian)
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
sudo ufw enable
sudo ufw status verbose
Step 5: Install and Configure fail2ban
Automated brute-force protection. Bans IPs after repeated failures.
sudo apt install fail2ban -y
# Create a local config (never edit jail.conf directly)
sudo tee /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
banaction = ufw
[sshd]
enabled = true
port = 22
logpath = %(sshd_log)s
maxretry = 3
bantime = 86400
EOF
sudo systemctl enable fail2ban
sudo systemctl restart fail2ban
# Check status and banned IPs
sudo fail2ban-client status sshd
Step 6: Enable Automatic Security Updates
Unattended upgrades ensure critical patches are applied without human intervention.
# Debian/Ubuntu
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades
# Verify it's enabled
cat /etc/apt/apt.conf.d/20auto-upgrades
# Should show:
# APT::Periodic::Update-Package-Lists "1";
# APT::Periodic::Unattended-Upgrade "1";
Step 7: Remove Unnecessary Services
Every running service is an attack surface. Remove what you don't need.
# List all listening services
ss -tulnp
# List enabled services
systemctl list-unit-files --state=enabled --type=service
# Disable and stop services you don't need
sudo systemctl disable --now cups snapd avahi-daemon bluetooth
sudo apt purge telnet rsh-client rsh-redone-client 2>/dev/null
Step 8: Configure Audit Logging
The Linux audit system records security-relevant events. Essential for incident response.
sudo apt install auditd -y
# Add audit rules for critical files
sudo tee /etc/audit/rules.d/hardening.rules << 'EOF'
# Monitor password and group changes
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
# Monitor SSH configuration
-w /etc/ssh/sshd_config -p wa -k sshd_config
-w /etc/ssh/sshd_config.d/ -p wa -k sshd_config
# Monitor cron
-w /etc/crontab -p wa -k cron
-w /var/spool/cron/ -p wa -k cron
# Log all commands run as root
-a always,exit -F arch=b64 -F euid=0 -S execve -k root_commands
EOF
sudo systemctl restart auditd
# Search audit logs
sudo ausearch -k identity --start today
Step 9: Kernel Hardening via sysctl
Harden the network stack and memory behavior at the kernel level.
sudo tee /etc/sysctl.d/99-security.conf << 'EOF'
# Disable IP forwarding (unless this is a router)
net.ipv4.ip_forward = 0
# Disable ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
# Enable reverse path filtering (anti-spoofing)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Ignore ICMP broadcast requests
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Enable SYN cookies (SYN flood protection)
net.ipv4.tcp_syncookies = 1
# Disable core dumps for setuid programs
fs.suid_dumpable = 0
# Randomize address space layout (ASLR)
kernel.randomize_va_space = 2
# Restrict access to kernel pointers
kernel.kptr_restrict = 2
# Restrict dmesg access to root
kernel.dmesg_restrict = 1
EOF
sudo sysctl --system
Step 10: Set File Permissions on Critical Files
# Restrict access to sensitive files
sudo chmod 600 /etc/shadow /etc/gshadow
sudo chmod 644 /etc/passwd /etc/group
sudo chmod 700 /root
sudo chmod 600 /boot/grub/grub.cfg
# Find world-writable files (potential security risk)
find / -xdev -type f -perm -0002 -exec ls -l {} \; 2>/dev/null
# Find files with no owner
find / -xdev -nouser -o -nogroup 2>/dev/null
Steps 11-15: Quick Wins
# Step 11: Disable USB storage (if not needed)
echo "blacklist usb-storage" | sudo tee /etc/modprobe.d/blacklist-usb.conf
# Step 12: Set login banner
sudo tee /etc/issue.net << 'EOF'
Authorized access only. All activity is monitored and logged.
EOF
# Step 13: Configure password policy
sudo apt install libpam-pwquality -y
# In /etc/security/pwquality.conf: minlen=14, dcredit=-1, ucredit=-1
# Step 14: Set account lockout
# In /etc/pam.d/common-auth, add:
# auth required pam_tally2.so deny=5 onerr=fail unlock_time=900
# Step 15: Restrict cron to authorized users only
echo "deployer" | sudo tee /etc/cron.allow
sudo chmod 600 /etc/cron.allow
Steps 16-20: Advanced Hardening
Step 16: Enable Process Accounting
sudo apt install acct -y
sudo systemctl enable acct
# View login history
lastlog
# View command accounting
lastcomm deployer
Step 17: Configure Logrotate for Audit Logs
sudo tee /etc/logrotate.d/audit << 'EOF'
/var/log/audit/audit.log {
weekly
rotate 52
compress
delaycompress
notifempty
create 0600 root root
postrotate
/usr/sbin/service auditd restart
endscript
}
EOF
Step 18: Disable IPv6 (If Not Used)
# Add to /etc/sysctl.d/99-security.conf
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
Step 19: Set GRUB Password
# Generate a hashed password
sudo grub-mkpasswd-pbkdf2
# Copy the hash and add to /etc/grub.d/40_custom:
# set superusers="admin"
# password_pbkdf2 admin <hash>
sudo update-grub
Step 20: Run a Security Audit with Lynis
# Install Lynis
sudo apt install lynis -y
# Run a full system audit
sudo lynis audit system
# Check the hardening index (aim for 80+)
grep "hardening_index" /var/log/lynis-report.dat
Lynis scores your system on a 0-100 hardening index and provides specific recommendations. Run it after applying all the steps above — you should score 80+.
Verification Checklist
After applying all 20 steps, verify your work:
# Quick verification script
echo "=== SSH Config ==="
sshd -T | grep -E "permitrootlogin|passwordauthentication|pubkeyauthentication"
echo "=== Firewall ==="
sudo ufw status | head -20
echo "=== Fail2ban ==="
sudo fail2ban-client status
echo "=== Listening Services ==="
ss -tulnp | grep LISTEN
echo "=== Kernel Hardening ==="
sysctl net.ipv4.conf.all.rp_filter net.ipv4.tcp_syncookies kernel.randomize_va_space
echo "=== File Permissions ==="
stat -c '%a %n' /etc/shadow /etc/passwd /etc/ssh/sshd_config
Now that your server is hardened, you need to know how to troubleshoot it when things go wrong — next we cover strace, lsof, tcpdump, and systematic debugging methodology.
