A default Linux installation is not secure. It’s designed to work, not to be hardened. Attackers know exactly what a default Ubuntu, CentOS, or Debian server looks like — and they exploit the predictable configuration every day. This guide gives you every command you need to harden a Linux server from scratch.
1. Update Everything First
# Ubuntu/Debian
sudo apt update && sudo apt upgrade -y && sudo apt autoremove -y
# RHEL/CentOS/Rocky Linux
sudo dnf update -y && sudo dnf autoremove -y
# Enable automatic security updates
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
# RHEL equivalent
sudo dnf install dnf-automatic
sudo systemctl enable --now dnf-automatic-install.timer2. Create a Non-Root Admin User
# Create new admin user
sudo useradd -m -s /bin/bash -G sudo secadmin
# Set strong password
sudo passwd secadmin
# Or better: use SSH keys and disable password auth
sudo mkdir -p /home/secadmin/.ssh
sudo chmod 700 /home/secadmin/.ssh
sudo nano /home/secadmin/.ssh/authorized_keys
# Paste your public key here
sudo chmod 600 /home/secadmin/.ssh/authorized_keys
sudo chown -R secadmin:secadmin /home/secadmin/.ssh3. Harden SSH Configuration
SSH is the #1 attack surface on internet-facing Linux servers. These settings dramatically reduce exposure:
# Edit SSH config
sudo nano /etc/ssh/sshd_config
# Paste these settings (adjust port to something non-standard)
Port 2222
Protocol 2
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
MaxAuthTries 3
MaxSessions 2
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
UseDNS no
Banner /etc/ssh/banner.txt
# Allow only specific users
AllowUsers secadmin
# Restart SSH
sudo systemctl restart sshd
# Test config before logging out!
ssh -p 2222 secadmin@server-ip4. Configure UFW Firewall
# Install and configure UFW (Uncomplicated Firewall)
sudo apt install ufw
# Default deny everything
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow only what you need
sudo ufw allow 2222/tcp # Your SSH port
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
# Rate limit SSH to prevent brute force
sudo ufw limit 2222/tcp
# Enable firewall
sudo ufw enable
sudo ufw status verbose
# For RHEL with firewalld:
sudo systemctl start firewalld
sudo firewall-cmd --set-default-zone=drop
sudo firewall-cmd --add-service=ssh --permanent
sudo firewall-cmd --add-port=443/tcp --permanent
sudo firewall-cmd --reload5. Install and Configure Fail2ban
# Install fail2ban
sudo apt install fail2ban
# Create local config (never edit the main config)
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
backend = systemd
[sshd]
enabled = true
port = 2222
logpath = /var/log/auth.log
maxretry = 3
bantime = 86400
# Start fail2ban
sudo systemctl enable --now fail2ban
# Check status
sudo fail2ban-client status
sudo fail2ban-client status sshd
# View banned IPs
sudo fail2ban-client get sshd banned6. Kernel Hardening with sysctl
# Edit /etc/sysctl.d/99-security.conf
sudo nano /etc/sysctl.d/99-security.conf
# Paste all of the following:
# 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 SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
# Disable source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# Ignore ICMP broadcast requests (Smurf attack protection)
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Enable reverse path filtering (spoofed packets)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Disable IPv6 if not needed
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
# Restrict dmesg to root
kernel.dmesg_restrict = 1
# Disable kexec (prevents kernel replacement)
kernel.kexec_load_disabled = 1
# Apply immediately
sudo sysctl -p /etc/sysctl.d/99-security.conf7. Configure AppArmor / SELinux
# Ubuntu: AppArmor (usually pre-installed)
sudo systemctl status apparmor
sudo aa-status
# Enable AppArmor profiles
sudo aa-enforce /etc/apparmor.d/*
# Install additional profiles
sudo apt install apparmor-profiles apparmor-utils
# RHEL: Check SELinux status
sestatus
getenforce
# Set to enforcing mode
sudo setenforce 1
sudo sed -i 's/SELINUX=permissive/SELINUX=enforcing/' /etc/selinux/config
# Check SELinux audit log for denials
sudo ausearch -m avc -ts recent | audit2why8. File System Security
# Find world-writable files (attackers love these)
sudo find / -xdev -type f -perm -0002 -print
# Find SUID/SGID binaries (potential privilege escalation)
sudo find / -xdev -perm /4000 -type f -print 2>/dev/null
sudo find / -xdev -perm /2000 -type f -print 2>/dev/null
# Remove unnecessary SUID bits
sudo chmod u-s /usr/bin/at # Example
sudo chmod u-s /usr/bin/chfn
# Secure /tmp mount (prevent execution of code from /tmp)
# Add to /etc/fstab:
tmpfs /tmp tmpfs defaults,nodev,nosuid,noexec 0 0
# Apply immediately
sudo mount -o remount /tmp
# Set proper permissions on sensitive files
sudo chmod 640 /etc/shadow
sudo chmod 644 /etc/passwd
sudo chmod 600 /etc/ssh/sshd_config
sudo chattr +i /etc/resolv.conf # Make immutable9. User Account Security
# Find all accounts with UID 0 (root-level)
awk -F: '($3 == 0) { print $1 }' /etc/passwd
# Lock unused accounts
sudo usermod -L daemon
sudo usermod -L bin
sudo usermod -L sync
# Set password aging policies
sudo nano /etc/login.defs
PASS_MAX_DAYS 90
PASS_MIN_DAYS 1
PASS_WARN_AGE 14
# Apply to existing users
sudo chage -M 90 -m 1 -W 14 username
# Check users with empty passwords
sudo awk -F: '($2 == "" ) { print $1 }' /etc/shadow
# Configure PAM password quality
sudo apt install libpam-pwquality
sudo nano /etc/pam.d/common-password
# Add: minlen=14 dcredit=-1 ucredit=-1 ocredit=-1 lcredit=-110. Audit Logging with auditd
# Install auditd
sudo apt install auditd audispd-plugins
# Configure rules /etc/audit/rules.d/audit.rules
-D # Delete all existing rules
-b 8192 # Buffer size
# Monitor authentication
-w /var/log/auth.log -p wa -k authentication
# Monitor /etc/passwd and /etc/shadow changes
-w /etc/passwd -p wa -k user_modification
-w /etc/shadow -p wa -k user_modification
-w /etc/sudoers -p wa -k sudo_changes
# Monitor privileged commands
-a always,exit -F arch=b64 -S execve -F euid=0 -k root_commands
# Monitor network configuration changes
-w /etc/hosts -p wa -k network_config
-w /etc/network/ -p wa -k network_config
# Reload and start
sudo service auditd restart
# Search audit logs
sudo ausearch -k authentication | aureport
sudo aureport --summary -i
sudo ausearch -ua root -ts today | tail -5011. Disable Unnecessary Services
# List all running services
sudo systemctl list-units --type=service --state=running
# Disable services you don't need (examples)
sudo systemctl disable --now avahi-daemon # mDNS
sudo systemctl disable --now cups # Printing
sudo systemctl disable --now bluetooth # Bluetooth
sudo systemctl disable --now nfs-server # NFS
sudo systemctl disable --now rpcbind # RPC
# Check for listening services
ss -tulnp
netstat -tulnp
# Remove unneeded packages entirely
sudo apt purge telnet rsh-client rsh-server xinetd nis tftp12. Install a Rootkit Scanner
# Install rkhunter and chkrootkit
sudo apt install rkhunter chkrootkit
# Run initial scan
sudo rkhunter --update
sudo rkhunter --check --sk
# View results
sudo cat /var/log/rkhunter.log | grep -E "WARNING|FOUND"
# Run chkrootkit
sudo chkrootkit
# Schedule weekly scans via cron
echo "0 2 * * 0 root /usr/bin/rkhunter --check --sk --report-warnings-only" | sudo tee /etc/cron.d/rkhunterSecurity Hardening Score
Run Lynis to get an overall security score and prioritized recommendations:
# Run Lynis audit
sudo apt install lynis
sudo lynis audit system
# Check score
sudo cat /var/log/lynis.log | grep "Hardening index"
# View recommendations
sudo cat /var/log/lynis-report.dat | grep "^suggestion"A freshly hardened server typically scores 75-85 on Lynis. A stock default Ubuntu server scores around 55-60. Keep running Lynis monthly to catch regressions. A properly hardened Linux server will block the overwhelming majority of automated attacks that constantly scan the internet for vulnerable hosts.