Linux servers are the backbone of so much of the internet, and keeping them secure is a constant battle. But the real trick to hardening a Linux server isn’t just about applying a bunch of patches; it’s about understanding that security is a continuous process of reducing your attack surface, not a one-time event.

Let’s look at a typical hardened Linux server in action. Imagine a busy web server. We’ve got Nginx listening on ports 80 and 443, serving up static assets and proxying dynamic requests to a backend application server. The system itself is running a minimal set of services, with strict firewall rules in place.

Here’s a simplified netstat -tulnp output from such a server:

tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      1234/sshd
tcp        0      0 127.0.0.1:5432          0.0.0.0:*               LISTEN      5678/postgres
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      9101/nginx: master
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      9101/nginx: master
tcp6       0      0 :::22                   :::*                    LISTEN      1234/sshd
tcp6       0      0 :::80                   :::*                    LISTEN      9101/nginx: master
tcp6       0      0 :::443                  :::*                    LISTEN      9101/nginx: master

Notice a few things: only essential services (sshd, postgres, nginx) are listening. postgres is bound to 127.0.0.1 for local access only, meaning it’s not exposed to the network.

The core problem hardening solves is that a default Linux installation often has more services running and more ports open than strictly necessary, creating a larger "attack surface" for malicious actors. Each service, each open port, is a potential entry point. Hardening systematically closes off these unnecessary pathways.

The mental model for hardening is layered defense:

  1. Minimize Attack Surface: Run only what you need.
  2. Secure Services: Configure running services to be as restrictive as possible.
  3. Control Access: Implement strong authentication and authorization.
  4. Monitor and Audit: Detect and respond to suspicious activity.
  5. Keep Updated: Patch vulnerabilities promptly.

Let’s walk through some key areas:

1. SSH Security

  • Problem: Default SSH configuration is often too permissive.
  • Configuration (/etc/ssh/sshd_config):
    Port 2222
    PermitRootLogin no
    PasswordAuthentication no
    ChallengeResponseAuthentication no
    UsePAM yes
    AllowUsers user1 user2
    
  • Why it works:
    • Port 2222: Moves SSH off the default port 22, reducing automated bot scans.
    • PermitRootLogin no: Prevents direct root login, forcing users to su or sudo from a regular account.
    • PasswordAuthentication no: Disables password logins, forcing the use of SSH keys, which are much harder to brute-force.
    • AllowUsers user1 user2: Explicitly defines who can log in, denying access to all others.
  • To implement: Edit /etc/ssh/sshd_config, then sudo systemctl restart sshd.

2. Firewall (iptables/nftables)

  • Problem: Unrestricted network access.
  • Configuration (iptables example):
    # Default policies: DROP all incoming, allow all outgoing
    iptables -P INPUT DROP
    iptables -P FORWARD DROP
    iptables -P OUTPUT ACCEPT
    
    # Allow loopback traffic
    iptables -A INPUT -i lo -j ACCEPT
    iptables -A OUTPUT -o lo -j ACCEPT
    
    # Allow established and related connections
    iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
    
    # Allow SSH (on our custom port)
    iptables -A INPUT -p tcp --dport 2222 -j ACCEPT
    
    # Allow HTTP/HTTPS
    iptables -A INPUT -p tcp --dport 80 -j ACCEPT
    iptables -A INPUT -p tcp --dport 443 -j ACCEPT
    
    # Save rules (e.g., using iptables-persistent)
    sudo apt-get install iptables-persistent -y
    sudo netfilter-persistent save
    
  • Why it works: The DROP policy means any traffic not explicitly allowed is blocked. We then allow only necessary inbound traffic (SSH, HTTP, HTTPS) and essential system traffic (loopback, established connections).
  • To implement: Run the iptables commands, then save them.

3. User and Group Management

  • Problem: Unnecessary or overly privileged users.
  • Check: getent passwd and getent group.
  • Fix: Remove unused accounts. For example, if user3 is no longer needed:
    sudo userdel -r user3
    
  • Why it works: Every user account is a potential login target. Removing inactive or unnecessary accounts reduces this surface. The -r flag removes the user’s home directory.

4. Service Management (systemd)

  • Problem: Unnecessary services running.
  • Check: sudo systemctl list-units --type=service --state=running
  • Fix: Disable and stop services not required. For example, if apache2 is not needed:
    sudo systemctl stop apache2
    sudo systemctl disable apache2
    
  • Why it works: Services consume resources and can have vulnerabilities. Stopping and disabling unneeded services frees up resources and closes potential attack vectors.

5. File Permissions

  • Problem: Overly permissive file and directory permissions.
  • Check: ls -l /etc/shadow, ls -l /etc/passwd
  • Fix: Ensure sensitive files like /etc/shadow are only readable by root:
    sudo chmod 644 /etc/passwd
    sudo chmod 640 /etc/shadow
    
  • Why it works: /etc/shadow contains hashed passwords. Restricting its read access to root prevents non-privileged users from even attempting to crack them offline. /etc/passwd can be world-readable, but /etc/shadow must not be.

6. Kernel Parameter Tuning (sysctl)

  • Problem: Default kernel settings can be vulnerable to certain attacks (e.g., SYN floods).
  • Configuration (/etc/sysctl.conf):
    net.ipv4.tcp_syncookies = 1
    net.ipv4.ip_forward = 0
    net.ipv4.conf.all.accept_redirects = 0
    net.ipv4.conf.default.accept_redirects = 0
    net.ipv4.conf.all.send_redirects = 0
    net.ipv4.conf.default.send_redirects = 0
    kernel.randomize_va_space = 2
    
  • Why it works:
    • tcp_syncookies = 1: Helps mitigate SYN flood attacks by using a stateless cookie mechanism.
    • ip_forward = 0: Prevents the server from acting as a router, which is usually not intended for a hardened endpoint.
    • accept_redirects = 0, send_redirects = 0: Prevents ICMP redirects, which can be used to hijack traffic.
    • randomize_va_space = 2: Enhances ASLR (Address Space Layout Randomization) for better protection against buffer overflow exploits.
  • To implement: Edit /etc/sysctl.conf and run sudo sysctl -p.

7. Package Management and Updates

  • Problem: Unpatched vulnerabilities.
  • Check: sudo apt list --upgradable (for Debian/Ubuntu) or sudo yum check-update (for RHEL/CentOS).
  • Fix: Regularly update packages:
    sudo apt update && sudo apt upgrade -y
    # or
    sudo yum update -y
    
  • Why it works: Developers constantly fix security flaws. Applying updates ensures your system benefits from these fixes, closing known exploit paths.

8. Intrusion Detection/Prevention (Fail2ban)

  • Problem: Brute-force attacks against services like SSH.
  • Configuration (/etc/fail2ban/jail.local):
    [sshd]
    enabled = true
    port = 2222
    filter = sshd
    logpath = /var/log/auth.log
    maxretry = 3
    bantime = 1h
    
  • Why it works: Fail2ban monitors log files for repeated failed login attempts and automatically updates firewall rules to block the offending IP addresses for a specified bantime.
  • To implement: Install fail2ban, create jail.local, and start/enable the service.

9. Auditing (auditd)

  • Problem: Lack of visibility into system access and changes.
  • Configuration (/etc/audit/rules.d/audit.rules):
    -w /etc/passwd -p rwxa -k passwd_changes
    -w /etc/shadow -p rwxa -k shadow_changes
    -w /etc/sudoers -p rwxa -k sudoers_changes
    -a always,exit -F arch=b64 -S unlink,unlinkat -k delete_files
    -a always,exit -F arch=b32 -S unlink,unlinkat -k delete_files
    
  • Why it works: auditd logs detailed information about system events, such as file modifications, command executions, and login attempts. This is crucial for forensic analysis after a security incident.
  • To implement: Install auditd, add rules, and start/enable the service.

10. File Integrity Monitoring (aide/tripwire)

  • Problem: Detecting unauthorized modifications to critical system files.
  • Check: Run sudo aide --check (after initial database creation).
  • Why it works: Tools like AIDE (Advanced Intrusion Detection Environment) create a database of file checksums and metadata. Regularly running a check against this database will flag any files that have been tampered with since the last check.
  • To implement: Install aide, initialize the database (sudo aide --init), and then run checks.

One of the most overlooked aspects of hardening is the careful management of sudoers. Most administrators grant broad sudo privileges, but a more secure approach is to use specific command-level permissions. For example, instead of allowing a user to run any command as root, you might restrict them to only being able to restart a specific service:

%admin ALL=(ALL) ALL  # This is too broad

becomes:

%webadmins ALL=/usr/sbin/service nginx restart,/usr/sbin/systemctl restart nginx.service

This significantly limits the potential damage a compromised account can do.

The next hurdle you’ll likely encounter after implementing these steps is setting up robust logging and log aggregation, so you can actually see what’s happening across your hardened fleet.

Want structured learning?

Take the full Linux & Systems Programming course →