A fresh Linux server installation is not secure by default. Within minutes of going online, automated bots will discover your server and begin probing for vulnerabilities. This guide covers the essential hardening steps every Linux administrator should implement.

Initial System Setup

Update Everything First

Before any configuration, ensure your system is fully updated:

# Debian/Ubuntu
sudo apt update && sudo apt upgrade -y

# RHEL/CentOS/Rocky
sudo dnf update -y

# Check for and install security updates specifically
sudo apt list --upgradable | grep -i security

Set up automatic security updates to ensure critical patches are applied promptly:

# Debian/Ubuntu
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

Create a Non-Root User

Never use root for daily operations. Create a dedicated admin user:

# Create user with home directory
sudo adduser adminuser

# Add to sudo group
sudo usermod -aG sudo adminuser

# Verify sudo access
su - adminuser
sudo whoami  # Should return "root"

SSH Hardening

SSH is your primary attack surface. Secure it thoroughly.

Basic SSH Configuration

Edit /etc/ssh/sshd_config:

# Disable root login
PermitRootLogin no

# Disable password authentication (after setting up keys!)
PasswordAuthentication no

# Disable empty passwords
PermitEmptyPasswords no

# Use SSH Protocol 2 only
Protocol 2

# Limit authentication attempts
MaxAuthTries 3

# Set login grace time
LoginGraceTime 60

# Disable X11 forwarding (unless needed)
X11Forwarding no

# Specify allowed users (whitelist)
AllowUsers adminuser deployuser

# Use strong ciphers only
Ciphers aes256-gcm@openssh.com,chacha20-poly1305@openssh.com,aes256-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com

Set Up SSH Key Authentication

Generate a strong key pair on your local machine:

# Generate Ed25519 key (recommended)
ssh-keygen -t ed25519 -C "admin@yourdomain.com"

# Or RSA with 4096 bits
ssh-keygen -t rsa -b 4096 -C "admin@yourdomain.com"

Copy the public key to your server:

ssh-copy-id -i ~/.ssh/id_ed25519.pub adminuser@yourserver

Critical: Test key-based login in a new terminal before disabling passwords!

# Restart SSH after configuration changes
sudo systemctl restart sshd

Optional: Change SSH Port

While not true security (security through obscurity), changing the SSH port reduces log noise from automated scanners:

# In /etc/ssh/sshd_config
Port 2222

# Don't forget to update firewall rules!
sudo ufw allow 2222/tcp
sudo ufw delete allow 22/tcp

Firewall Configuration

UFW (Uncomplicated Firewall)

UFW is the simplest option for most deployments:

# Install UFW
sudo apt install ufw

# Default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH (use your custom port if changed)
sudo ufw allow 22/tcp

# Allow web traffic
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Enable firewall
sudo ufw enable

# Check status
sudo ufw status verbose

iptables (Advanced)

For more granular control:

# Flush existing rules
sudo iptables -F

# Default policies
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT

# Allow loopback
sudo iptables -A INPUT -i lo -j ACCEPT

# Allow established connections
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow SSH
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Allow HTTP/HTTPS
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# Save rules (Debian/Ubuntu)
sudo apt install iptables-persistent
sudo netfilter-persistent save

Fail2ban: Brute Force Protection

Fail2ban monitors log files and bans IPs that show malicious behavior.

Installation and Basic Setup

# Install
sudo apt install fail2ban

# Create local configuration (don't edit jail.conf directly)
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Configure SSH Protection

Edit /etc/fail2ban/jail.local:

[DEFAULT]
# Ban duration (1 hour)
bantime = 3600

# Time window for counting failures
findtime = 600

# Number of failures before ban
maxretry = 3

# Email notifications (optional)
destemail = admin@yourdomain.com
sender = fail2ban@yourdomain.com
action = %(action_mwl)s

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 86400

Manage Fail2ban

# Start and enable
sudo systemctl start fail2ban
sudo systemctl enable fail2ban

# Check status
sudo fail2ban-client status
sudo fail2ban-client status sshd

# Unban an IP
sudo fail2ban-client set sshd unbanip 192.168.1.100

# View banned IPs
sudo fail2ban-client get sshd banned

User and Permission Security

Password Policies

Configure password requirements in /etc/login.defs:

PASS_MAX_DAYS   90
PASS_MIN_DAYS   1
PASS_MIN_LEN    12
PASS_WARN_AGE   14

Install and configure PAM for stronger password policies:

# Install password quality library
sudo apt install libpam-pwquality

# Edit /etc/pam.d/common-password
password requisite pam_pwquality.so retry=3 minlen=12 difok=3 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1

Disable Unused Accounts

# List all users with login shells
grep -E '/bin/(bash|sh)' /etc/passwd

# Lock unused accounts
sudo usermod -L username

# Set shell to nologin for service accounts
sudo usermod -s /usr/sbin/nologin serviceaccount

Secure sudo Configuration

# Edit sudoers safely
sudo visudo

Add these security measures:

# Require password for sudo (timeout in minutes)
Defaults timestamp_timeout=5

# Log all sudo commands
Defaults logfile="/var/log/sudo.log"

# Restrict sudo to specific commands for specific users
deployuser ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart myapp

Mandatory Access Control

AppArmor (Ubuntu/Debian)

AppArmor uses path-based security policies:

# Check status
sudo aa-status

# Install additional profiles
sudo apt install apparmor-profiles apparmor-profiles-extra

# Set a profile to enforce mode
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx

# View profile violations
sudo dmesg | grep apparmor

SELinux (RHEL/CentOS)

SELinux provides label-based access control:

# Check status
sestatus

# Set to enforcing mode
sudo setenforce 1

# Make permanent in /etc/selinux/config
SELINUX=enforcing

# View denials
sudo ausearch -m avc -ts recent

# Generate policy module from denials
sudo audit2allow -a -M mypolicy
sudo semodule -i mypolicy.pp

System Auditing

Configure auditd

The Linux Audit Daemon provides detailed system monitoring:

# Install
sudo apt install auditd audispd-plugins

# Start and enable
sudo systemctl enable auditd
sudo systemctl start auditd

Add audit rules in /etc/audit/rules.d/audit.rules:

# Monitor authentication files
-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

# Monitor SSH configuration
-w /etc/ssh/sshd_config -p wa -k sshd

# Monitor successful and failed login attempts
-w /var/log/faillog -p wa -k logins
-w /var/log/lastlog -p wa -k logins

# Monitor privileged commands
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged

# Make configuration immutable (requires reboot to change)
-e 2

Apply rules:

sudo augenrules --load

Log Monitoring with Logwatch

# Install
sudo apt install logwatch

# Run manually
sudo logwatch --detail high --mailto admin@yourdomain.com --range today

# Configure daily reports in /etc/cron.daily/00logwatch

Network Security

Disable Unnecessary Services

# List all listening services
sudo ss -tulnp

# Disable unused services
sudo systemctl disable bluetooth
sudo systemctl disable cups
sudo systemctl disable avahi-daemon

# Remove if not needed
sudo apt remove telnetd rsh-server

Kernel Network Hardening

Add to /etc/sysctl.conf:

# Disable IP forwarding (unless router/firewall)
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0

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

# Disable ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0

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

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

# Ignore ICMP broadcasts
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Disable IPv6 if not used
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1

Apply changes:

sudo sysctl -p

File System Security

Set Proper Permissions

# Secure home directories
sudo chmod 700 /home/*

# Secure cron directories
sudo chmod 700 /etc/cron.*

# Remove world-writable permissions
sudo find / -xdev -type f -perm -0002 -exec chmod o-w {} \;

# Find SUID/SGID files (potential security risks)
sudo find / -xdev \( -perm -4000 -o -perm -2000 \) -type f -print

Mount Options

Edit /etc/fstab to add security options:

# Add noexec, nosuid, nodev to /tmp
tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev 0 0

# Add noexec to /var/log
/dev/sda3 /var/log ext4 defaults,noexec,nosuid,nodev 0 2

Vulnerability Scanning

Regular Security Audits

# Install Lynis
sudo apt install lynis

# Run audit
sudo lynis audit system

# View report
sudo cat /var/log/lynis-report.dat

Check for Rootkits

# Install rkhunter
sudo apt install rkhunter

# Update database
sudo rkhunter --update

# Run scan
sudo rkhunter --check --skip-keypress

Hardening Checklist Summary

Use this checklist for new server deployments:

  • Update all packages
  • Create non-root admin user
  • Configure SSH key authentication
  • Disable root SSH login
  • Disable password authentication
  • Configure firewall (ufw or iptables)
  • Install and configure fail2ban
  • Set up automatic security updates
  • Configure strong password policies
  • Enable AppArmor or SELinux
  • Set up auditd logging
  • Disable unnecessary services
  • Apply kernel network hardening
  • Set proper file permissions
  • Run Lynis security audit
  • Schedule regular security scans

Ongoing Maintenance

Security hardening isn’t a one-time task:

  1. Weekly: Review fail2ban logs, check for updates
  2. Monthly: Run Lynis audit, review user accounts
  3. Quarterly: Review firewall rules, audit SSH keys
  4. Annually: Full security assessment, update policies

The goal isn’t perfect security—that doesn’t exist. The goal is making your server harder to compromise than the next one, while maintaining the visibility to detect when something goes wrong.