Linux Security Hardening Basics¶
Overview¶
Every Linux server exposed to a network — whether on the public internet or a corporate VLAN — needs baseline hardening before it handles production workloads. Unpatched packages, open SSH with password authentication, missing firewalls, and unnecessary services create an attack surface that automated scanners exploit within minutes of exposure.
This tutorial covers practical hardening steps: SSH configuration, host firewalls (UFW and firewalld), patch management, an introduction to fail2ban, and alignment with CIS Benchmark principles. These controls form the foundation of DevSecOps and compliance programs.
This tutorial is part of Module 6: Storage, Logs, Networking & Operations in the REBASH Academy Linux series.
Prerequisites¶
- Complete SSH and Remote Administration and Linux Networking Essentials
- A non-production Linux VM for lab exercises (mistakes can lock you out)
sudoprivileges and console/out-of-band access as a safety net- Basic understanding of TCP ports and authentication
Learning Objectives¶
By the end of this tutorial, you will be able to:
- Harden SSH with key-based auth, disable root login, and restrict users
- Configure UFW (Debian/Ubuntu) or firewalld (RHEL) with minimal required rules
- Apply security updates and enable unattended patching where appropriate
- Explain fail2ban's role in blocking brute-force attacks
- Map hardening actions to CIS Benchmark categories for audit readiness
Theory¶
Defense in depth¶
Security is layered — no single control is sufficient:
- Network: Firewall, security groups, segmentation
- Access: SSH keys, MFA, sudo policies
- System: Patching, minimal packages, file permissions
- Detection: Logging, fail2ban, intrusion detection
- Response: Backups, incident runbooks, forensics capability
SSH hardening¶
SSH is the primary admin entry point and the most attacked service. Key settings in /etc/ssh/sshd_config:
| Directive | Recommended | Why |
|---|---|---|
PermitRootLogin | no or prohibit-password | Prevents direct root brute-force |
PasswordAuthentication | no | Forces key-based auth |
PubkeyAuthentication | yes | Enable public key login |
PermitEmptyPasswords | no | Block empty password accounts |
MaxAuthTries | 3 | Limit brute-force attempts per connection |
AllowUsers / AllowGroups | Specific admins | Restrict who can SSH |
X11Forwarding | no | Disable unless needed |
Protocol | 2 (default on modern) | Legacy check |
Always validate with sshd -t before restarting. Keep a second session open during changes.
Host firewalls¶
UFW (Uncomplicated Firewall) on Debian/Ubuntu wraps iptables/nftables:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enable
firewalld on RHEL/Fedora uses zones:
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --remove-service=cockpit # example
sudo firewall-cmd --reload
Principle: default deny inbound, allow only required ports (SSH, HTTP/S, app-specific).
Patch management¶
Unpatched CVEs are a leading breach vector:
- Debian/Ubuntu:
apt update && apt upgrade; unattended-upgrades for automation - RHEL:
dnf update; dnf-automatic or Satellite
Schedule regular maintenance windows; test patches in staging first.
fail2ban¶
fail2ban monitors log files (e.g., /var/log/auth.log, journal) for failed login patterns and temporarily bans offending IPs via firewall rules.
Typical /etc/fail2ban/jail.local:
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600
Not a substitute for key-only SSH — it reduces noise from internet-wide scanning.
CIS Benchmark basics¶
The CIS Linux Benchmark provides prioritized controls:
| Category | Examples |
|---|---|
| Initial setup | Filesystem partitions, bootloader permissions |
| Services | Disable unused services (avahi, cups) |
| Network | Disable unused protocols, host firewall |
| Logging and auditing | rsyslog/journald, auditd |
| Access control | Password policies, sudo logging |
| System maintenance | Patch process, integrity checking |
Use CIS-CAT or Lynis for automated assessment. Level 1 = essential; Level 2 = stricter (may impact usability).
Additional hardening¶
- Remove or disable unused packages and services
- Configure
sudowith logging (/var/log/auth.log) - Set
/etc/login.defspassword aging policies - Enable SELinux (RHEL) or AppArmor (Ubuntu) — do not disable without reason
- Restrict file permissions on sensitive files (
/etc/shadow, SSH keys)
Hands-on Lab¶
Step 1 – Baseline security audit¶
echo "=== Listening ports ==="
sudo ss -tulpn
echo "=== Failed SSH attempts (last hour) ==="
sudo journalctl -u ssh -u sshd --since "1 hour ago" 2>/dev/null | grep -i failed | tail -5 || true
echo "=== Pending updates ==="
sudo apt update 2>/dev/null && apt list --upgradable 2>/dev/null | head -5 || \
sudo dnf check-update 2>/dev/null | head -5 || true
Expected output: List of open ports, any failed auth lines, and available security updates.
Step 2 – Review current SSH configuration¶
sudo sshd -T | grep -E "permitrootlogin|passwordauthentication|pubkeyauthentication|maxauthtries"
grep -E "^(PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|MaxAuthTries)" \
/etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null
Expected output: Effective settings — note whether password auth is enabled (common default on fresh installs).
Step 3 – Harden SSH (lab VM only)¶
Create a drop-in config (safer than editing main file):
sudo tee /etc/ssh/sshd_config.d/99-hardening.conf << 'EOF'
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
X11Forwarding no
AllowUsers YOUR_USERNAME
EOF
# Replace YOUR_USERNAME with your actual user before applying:
sudo sed -i "s/YOUR_USERNAME/$USER/" /etc/ssh/sshd_config.d/99-hardening.conf
sudo sshd -t && echo "Config syntax OK"
Expected output: Config syntax OK — do not restart until you confirm key-based login works.
Verify key auth in a second terminal before:
Step 4 – Configure UFW (Ubuntu/Debian)¶
sudo ufw status verbose 2>/dev/null || echo "UFW not installed"
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw --force enable
sudo ufw status numbered
Expected output:
Step 4b – Configure firewalld (RHEL alternative)¶
sudo firewall-cmd --state 2>/dev/null || echo "firewalld not active"
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
Expected output: services: ssh dhcpv6-client in the public zone.
Step 5 – Apply security updates¶
Expected output: Packages upgraded or "0 upgraded" if current.
Step 6 – Install and configure fail2ban¶
sudo apt install -y fail2ban 2>/dev/null || sudo dnf install -y fail2ban 2>/dev/null || true
sudo tee /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
[sshd]
enabled = true
EOF
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd 2>/dev/null || sudo fail2ban-client status
Expected output: fail2ban running with sshd jail enabled.
Step 7 – Disable unnecessary services¶
systemctl list-unit-files --type=service --state=enabled | \
grep -E "bluetooth|cups|avahi" || echo "No common unnecessary services enabled"
# Example disable (if present and unused):
# sudo systemctl disable --now avahi-daemon
Expected output: Audit of enabled services; disable only what you confirm is unused.
Step 8 – Run Lynis quick audit (optional)¶
sudo apt install -y lynis 2>/dev/null || sudo dnf install -y lynis 2>/dev/null || true
sudo lynis audit system --quick 2>/dev/null | tail -20
Expected output: Hardening index score and suggestions aligned with CIS-style checks.
Commands¶
| Command | Description |
|---|---|
sudo sshd -T | Dump effective SSH configuration |
sudo sshd -t | Test SSH config syntax |
sudo systemctl reload sshd | Apply SSH changes without dropping sessions |
sudo ufw enable | Enable UFW firewall |
sudo ufw allow 443/tcp | Allow HTTPS inbound |
sudo ufw status numbered | Show rules with index numbers |
sudo ufw delete N | Delete rule by index |
sudo firewall-cmd --list-all | Show firewalld zone config |
sudo firewall-cmd --permanent --add-port=8080/tcp | Open port permanently |
sudo apt update && sudo apt upgrade | Apply Debian/Ubuntu updates |
sudo dnf update | Apply RHEL/Fedora updates |
sudo fail2ban-client status sshd | fail2ban sshd jail status |
sudo fail2ban-client set sshd unbanip IP | Unban an IP address |
systemctl list-units --type=service --state=running | Audit running services |
sudo lynis audit system | Security audit scan |
sudo aa-status | AppArmor status (Ubuntu) |
getenforce | SELinux status (RHEL) |
Code Examples¶
SSH hardening drop-in (production template)¶
# /etc/ssh/sshd_config.d/99-production.conf
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers deploy admin
UFW web server profile¶
#!/bin/bash
set -euo pipefail
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
sudo ufw status verbose
Unattended upgrades (Debian/Ubuntu)¶
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
# Verify: /etc/apt/apt.conf.d/50unattended-upgrades
CIS-style service disable script¶
#!/bin/bash
# disable-unused-services.sh — review before running
for svc in avahi-daemon cups bluetooth; do
if systemctl is-enabled "$svc" &>/dev/null; then
echo "Disabling $svc"
sudo systemctl disable --now "$svc"
fi
done
Common Mistakes¶
Disabling password auth before testing SSH keys
You will lock yourself out. Always verify key login in a second session before PasswordAuthentication no.
Enabling firewall without allowing SSH
UFW/f firewalld with default deny and no SSH rule blocks your access. Allow SSH before enabling.
Editing sshd_config directly without drop-ins
Package updates can overwrite main config. Use /etc/ssh/sshd_config.d/*.conf for custom settings.
Installing fail2ban instead of fixing root cause
fail2ban mitigates brute force but does not replace key-only auth and network-level controls.
Applying CIS Level 2 blindly
Strict controls can break applications. Assess impact; implement Level 1 first.
Best Practices¶
Use infrastructure-as-code for hardening
Manage firewall rules, SSH config, and packages with Ansible, cloud-init, or golden AMIs.
Maintain break-glass access
Console access (cloud serial console, IPMI, hypervisor) for recovery when SSH is misconfigured.
Centralize authentication
LDAP, SSO, or certificate-based auth scales better than per-server key management.
Log and alert on auth failures
Ship /var/log/auth.log or journal SSH events to SIEM; alert on spikes.
Regular vulnerability scanning
Combine patching with Nessus, OpenSCAP, or cloud-native scanners for continuous assessment.
Troubleshooting¶
| Issue | Cause | Solution |
|---|---|---|
| Locked out after SSH change | Password disabled, no key | Use console; fix sshd_config; restore from backup |
sshd -t fails | Syntax error in config | Check drop-in files; remove invalid directives |
| UFW blocks application | Port not allowed | ufw allow PORT/tcp; verify with ss -tln |
| fail2ban banned your IP | Failed login attempts | fail2ban-client set sshd unbanip YOUR_IP |
| Updates require reboot | Kernel/libc patch | Schedule reboot; check /var/run/reboot-required |
| SELinux denies service | Policy violation | Check audit2why; set correct context or boolean |
| firewalld rules not active | Missing --permanent | Add --permanent then firewall-cmd --reload |
| Lynis low hardening score | Baseline defaults | Prioritize CIS Level 1 failures; track progress over time |
Summary¶
- Harden SSH first: disable root password login, enforce keys, limit users, validate with
sshd -t. - Enable host firewalls with default-deny inbound — UFW on Debian, firewalld on RHEL.
- Apply security patches regularly; automate with unattended-upgrades or equivalent.
- fail2ban adds brute-force protection but complements — not replaces — strong authentication.
- Map controls to CIS Benchmark categories for audit-ready baseline hardening.
Interview Questions¶
1. What SSH settings do you change first on a new production server?
Sample answer: Disable root login (PermitRootLogin no), disable password authentication, enable pubkey auth, set MaxAuthTries, and restrict AllowUsers. Validate config with sshd -t and test keys before reload.
2. UFW vs firewalld — when do you use each?
Sample answer: UFW is common on Debian/Ubuntu — simple syntax for iptables/nftables. firewalld on RHEL uses zones and runtime/permanent rules. Both implement host firewall; choice follows distribution.
3. What is fail2ban and how does it work?
Sample answer: fail2ban parses logs for patterns like failed SSH logins and adds firewall rules to block offending IPs temporarily. Configured via jails in /etc/fail2ban/jail.local.
4. Why disable password authentication if you have a strong password?
Sample answer: Passwords are vulnerable to brute force at scale. SSH keys are cryptographically stronger, not transmitted on each login, and pair well with automation. Password auth should be off for admin access.
5. What is the CIS Benchmark?
Sample answer: Center for Internet Security publishes consensus hardening guidelines for OS and software. Level 1 is practical baseline; Level 2 is stricter for high-security environments.
6. How do you safely apply SSH config changes?
Sample answer: Edit drop-in files, run sshd -t, keep an existing session open, reload sshd, test new connection in parallel before closing old session.
7. What ports should a minimal web server firewall allow?
Sample answer: SSH (22 or custom), HTTP (80), HTTPS (443), and any app-specific ports. Deny everything else inbound.
8. How do you verify a server is fully patched?
Sample answer: Run package manager check (apt list --upgradable, dnf check-update), review security advisories, confirm last update time, and check if reboot is required for kernel updates.
9. What is the principle of least privilege in Linux hardening?
Sample answer: Run services as non-root users, disable unused services, grant sudo only where needed, restrict SSH to admin accounts, and minimize open ports.
10. How does SELinux/AppArmor contribute to hardening?
Sample answer: Mandatory access control confines processes to allowed resources even if compromised. Policies limit damage from exploits beyond discretionary file permissions.
Related Tutorials¶
- Linux – Category Overview
- File Archiving and Compression (previous)
- Troubleshooting Linux Systems (next)
- SSH and Remote Administration
- Linux Networking Essentials
- User and Group Management
- Learning Paths – DevOps Engineer