Skip to content
Security Hardening — SSH Config, Firewall, fail2ban, SELinux, Auditing

Security Hardening — SSH Config, Firewall, fail2ban, SELinux, Auditing

DodaTech Updated Jun 20, 2026 11 min read

Security hardening is the process of reducing a system’s attack surface by configuring services, firewalls, access controls, and auditing. This guide covers the essential layers of Linux server security — SSH hardening, firewall rules, intrusion prevention, mandatory access control, and continuous auditing.

What You’ll Learn

You’ll configure SSH for key-only authentication, set up a stateful firewall with UFW and iptables, deploy fail2ban for brute-force protection, understand SELinux and AppArmor, and implement system auditing with auditd. Durga Antivirus Pro uses these exact hardening techniques in its deployment infrastructure.

Why Security Hardening Matters

Unsecured Linux servers are scanned by automated bots within minutes of going online. Default configurations — password SSH, open ports, no rate limiting — are easily exploited. Hardening turns a vulnerable default installation into a production-ready fortress. Every layer adds friction for attackers.

Learning Path

    flowchart LR
  A[Backup Strategies] --> B[Security Hardening<br/>You are here]
  B --> C[Shell Scripting]
  C --> D[Monitoring & Logging]
  D --> E[Infrastructure Automation]
  style B fill:#f90,color:#fff
  

SSH Hardening

The SSH daemon is the most exposed service on any Linux server. Harden it first.

# /etc/ssh/sshd_config — Hardened configuration
Port 2222                          # Change from default 22
Protocol 2                         # Disable legacy protocol 1

# Authentication
PermitRootLogin no                 # Never allow root SSH
PasswordAuthentication no          # Key-only authentication
PubkeyAuthentication yes
AuthenticationMethods publickey    # Enforce key-only
MaxAuthTries 2                     # Limit attempts

# Access control
AllowUsers alice bob               # Whitelist specific users
AllowGroups devops                 # Or whitelist groups
DenyUsers root                     # Explicit deny

# Connection hardening
ClientAliveInterval 300            # Check client every 5 min
ClientAliveCountMax 0              # Disconnect on inactivity
MaxSessions 4                      # Limit concurrent sessions
LoginGraceTime 30                  # 30 seconds to authenticate

# Cryptographic strength
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
KexAlgorithms curve25519-sha256,diffie-hellman-group16-sha512
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# Apply SSH hardening
sudo sshd -t                          # Test configuration
sudo systemctl reload sshd            # Reload without disconnecting

# Verify configuration
ssh -v -p 2222 user@server            # Connect to non-standard port
nmap -p 22 10.0.0.5                   # Verify port 22 is closed

SSH Key Management

# Generate strong Ed25519 key
ssh-keygen -t ed25519 -a 100 -f ~/.ssh/id_ed25519

# Copy key to server
ssh-copy-id -p 2222 user@server

# Or manual copy
cat ~/.ssh/id_ed25519.pub | ssh -p 2222 user@server \
    'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys'
chmod 600 ~/.ssh/authorized_keys
chmod 700 ~/.ssh

Firewall Configuration

UFW — Uncomplicated Firewall

# Enable UFW
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow specific services
sudo ufw allow 2222/tcp              # SSH on custom port
sudo ufw allow 80/tcp                # HTTP
sudo ufw allow 443/tcp               # HTTPS
sudo ufw allow from 10.0.0.0/8 to any port 3306  # MySQL from internal

# Rate limiting
sudo ufw limit 2222/tcp             # 6 connections per 30s

# Enable firewall
sudo ufw enable

# Status
sudo ufw status verbose

Expected sudo ufw status verbose:

Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing)
New profiles: skip

To                         Action      From
--                         ------      ----
2222/tcp                   LIMIT       Anywhere
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere
3306/tcp                   ALLOW       10.0.0.0/8

iptables — Low-Level Firewall

For complex rules that UFW can’t handle:

# Flush existing rules
sudo iptables -F
sudo iptables -X

# 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 2222 -m state --state NEW -j ACCEPT

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

# Rate limit SSH (10 connections per minute from a single IP)
sudo iptables -A INPUT -p tcp --dport 2222 -m state --state NEW \
    -m recent --set --name SSH
sudo iptables -A INPUT -p tcp --dport 2222 -m state --state NEW \
    -m recent --update --seconds 60 --hitcount 10 --name SSH -j DROP

# Save rules
sudo iptables-save > /etc/iptables/rules.v4

fail2ban — Intrusion Prevention

Fail2ban monitors log files for brute-force attempts and temporarily bans offending IPs:

# Install
sudo apt install fail2ban

# Main configuration
sudo tee /etc/fail2ban/jail.local <<EOF
[DEFAULT]
bantime = 3600                    # 1 hour ban
findtime = 600                    # 10 minute window
maxretry = 5                      # 5 attempts before ban
ignoreip = 127.0.0.1/8 10.0.0.0/8  # Never ban internal IPs

[sshd]
enabled = true
port = 2222
logpath = %(sshd_log)s

[nginx-http-auth]
enabled = true
port = http,https
logpath = %(nginx_error_log)s

[nginx-botsearch]
enabled = true
port = http,https
logpath = %(nginx_access_log)s
EOF

sudo systemctl enable --now fail2ban
# Check status
sudo fail2ban-client status
sudo fail2ban-client status sshd

# List banned IPs
sudo iptables -L f2b-sshd -n

# Manually unban
sudo fail2ban-client set sshd unbanip 203.0.113.42

Expected sudo fail2ban-client status sshd:

Status for the jail: sshd
|- Filter
|  |- Currently failed: 3
|  |- Total failed: 47
|  `- File list:    /var/log/auth.log
`- Actions
   |- Currently banned: 2
   |- Total banned: 15
   `- Banned IP list:    203.0.113.42 198.51.100.7

SELinux and AppArmor

Mandatory Access Control (MAC) systems restrict what processes can do, even if they run as root.

AppArmor (Ubuntu/Debian default)

# Check status
sudo aa-status

# Install apparmor-utils
sudo apt install apparmor-utils

# Profiles are in /etc/apparmor.d/
ls /etc/apparmor.d/

# Put a profile in enforce mode
sudo aa-enforce /path/to/binary

# Put in complain mode (log violations but don't block)
sudo aa-complain /path/to/binary

# Generate a profile from scratch
sudo aa-genprof /usr/bin/myapp
# Follow the prompts to create a policy

SELinux (RHEL/CentOS/Fedora)

# Check status
getenforce                    # Enforcing / Permissive / Disabled

# Switch modes
sudo setenforce 0             # Permissive (troubleshoot)
sudo setenforce 1             # Enforcing

# SELinux context
ls -Z /var/www/html/index.html
# system_u:object_r:httpd_sys_content_t:s0

# Fix context
sudo restorecon -Rv /var/www/html/
sudo chcon -t httpd_sys_content_t /path/to/file

# Create custom policy from audit log
sudo ausearch -m avc -ts recent | audit2allow -M myapp
sudo semodule -i myapp.pp

System Auditing with auditd

Auditd monitors system calls, file access, and user activity:

# Install
sudo apt install auditd audispd-plugins

# Add audit rules
sudo tee /etc/audit/rules.d/hardening.rules <<EOF
# Delete all existing rules
-D

# Buffer size
-b 8192

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

# Monitor system authentication
-w /etc/passwd -p wa -k passwd_changes
-w /etc/shadow -p wa -k shadow_changes
-w /etc/sudoers -p wa -k sudoers_changes

# Monitor cron
-w /etc/crontab -p wa -k cron

# Monitor binaries
-w /usr/bin/passwd -p x -k passwd_exec
-w /usr/bin/su -p x -k su_exec
-w /usr/bin/sudo -p x -k sudo_exec

# Track all root commands
-a exit,always -F arch=b64 -F uid=0 -S execve -k root_commands

# Monitor file deletion
-a exit,always -F arch=b64 -S unlink -S rmdir -S rename -k file_deletion
EOF

sudo systemctl enable --now auditd
# Search audit logs
sudo ausearch -k ssh_config       # SSH config changes
sudo ausearch -k passwd_changes   # Password file changes
sudo ausearch -k root_commands    # Commands run as root
sudo ausearch -m USER_LOGIN       # User login events

# Generate report
sudo aureport --summary
sudo aureport -l                  # Login report
sudo aureport -f                  # File access report

Expected sudo aureport --summary:

Summary Report
======================
Range of time in logs: 06/19/26 00:00:00.000 — 06/20/26 10:00:00.000
Selected time for report: all
Number of changes to configuration: 12
Number of changes to accounts, groups, or roles: 3
Number of logins: 45
Number of failed logins: 23
Number of authentication failures: 18
Number of users: 3
Number of terminals: 8
Number of host names: 4
Number of executables: 156
Number of files: 234
Number of AVC denials: 0
Number of anomalies: 2
Number of responses to anomalies: 0

Common Hardening Mistakes

1. Changing SSH Port but Not Updating Firewall

Changing Port 2222 in sshd_config without opening port 2222 in the firewall locks you out. Always update firewall rules before restarting SSH.

2. Applying firewalld Rules Over SSH Without a Safe Fallback

An iptables DROP default policy applied over SSH will drop your current session. Always add a delay or use at to schedule a revert: at now + 5 minutes <<< "iptables -P INPUT ACCEPT".

3. Disabling SELinux Instead of Configuring It

setenforce 0 isn’t a fix. It disables a security layer. Use audit2allow to create custom policies or switch to permissive mode temporarily to troubleshoot.

4. Overly Permissive fail2ban Settings

Banning after 2 failed attempts in 60 seconds will ban legitimate users who mistype passwords. Start with 5 attempts in 10 minutes and adjust.

5. Ignoring Audit Logs

Collecting audit events without reviewing them is security theater. Set up daily log review or integrate with a SIEM.

6. Allowing Root Login With Password

PermitRootLogin yes + PasswordAuthentication yes is the #1 reason servers get hacked. Disable both.

7. Forgetting About IPv6

IPv6 rules are separate from IPv4 in many tools. sudo ufw disable followed by sudo ufw enable resets both, but manual iptables rules need ip6tables equivalents.

Practice Questions

1. What is the difference between UFW and iptables? UFW is a frontend for iptables that simplifies common firewall rules. iptables provides low-level control over every packet filtering aspect. UFW suits most servers; iptables is needed for complex configurations.

2. How does fail2ban determine when to ban an IP? It monitors log files for patterns (failed SSH logins, 404 requests) and counts matches within a time window. When the count exceeds maxretry, it adds an iptables rule to drop packets from that IP.

3. What’s the difference between SELinux and AppArmor? SELinux uses a flexible role-based access control (RBAC) model with labels on every file and process. AppArmor uses path-based profiles that associate programs with file permissions. SELinux is more complex but more granular.

4. Why should you use SSH key authentication instead of passwords? SSH keys use asymmetric cryptography — even if the server database is compromised, keys can’t be reverse-engineered. Keys are also immune to brute-force attacks and can be passphrase-protected for additional security.

5. Challenge: You need to allow SSH access from only two IP addresses (company office and VPN), HTTP/HTTPS from anywhere, and database access from internal network only. Write the complete UFW or iptables configuration. Answer: sudo ufw default deny incoming, sudo ufw allow from OFFICE_IP to any port 2222, sudo ufw allow from VPN_IP to any port 2222, sudo ufw allow 80/tcp, sudo ufw allow 443/tcp, sudo ufw allow from 10.0.0.0/8 to any port 3306.

Mini Project: Hardening Automation Script

Create a script that applies standard hardening to a fresh Ubuntu server:

#!/bin/bash
# harden.sh — Apply standard server hardening
# Usage: sudo ./harden.sh
# Run on a fresh Ubuntu server

set -euo pipefail

echo "=== Server Hardening ==="

# 1. Update system
echo "Updating system packages..."
apt update && apt upgrade -y

# 2. Create admin user
if ! id -u admin >/dev/null 2>&1; then
    echo "Creating admin user..."
    useradd -m -s /bin/bash -G sudo admin
    mkdir -p /home/admin/.ssh
    cp "$HOME/.ssh/authorized_keys" /home/admin/.ssh/ 2>/dev/null || true
    chown -R admin:admin /home/admin/.ssh
    chmod 700 /home/admin/.ssh
    chmod 600 /home/admin/.ssh/authorized_keys
fi

# 3. Harden SSH
echo "Hardening SSH..."
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
cat > /etc/ssh/sshd_config << 'SSHCONF'
Port 2222
Protocol 2
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
MaxAuthTries 2
ClientAliveInterval 300
ClientAliveCountMax 0
MaxSessions 4
LoginGraceTime 30
SSHCONF
sshd -t && systemctl reload sshd

# 4. Configure firewall
echo "Configuring firewall..."
ufw default deny incoming
ufw default allow outgoing
ufw limit 2222/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable

# 5. Install and configure fail2ban
echo "Installing fail2ban..."
apt install -y fail2ban
cat > /etc/fail2ban/jail.local << 'FAIL2BAN'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
ignoreip = 127.0.0.1/8 10.0.0.0/8

[sshd]
enabled = true
port = 2222
logpath = %(sshd_log)s
FAIL2BAN
systemctl enable --now fail2ban

# 6. Install and configure auditd
echo "Configuring auditd..."
apt install -y auditd
auditctl -e 1

# 7. Set restrictive umask
echo "umask 027" >> /etc/profile

# 8. Disable unused services
echo "Disabling unused services..."
systemctl disable --now cups 2>/dev/null || true
systemctl disable --now avahi-daemon 2>/dev/null || true

echo ""
echo "=== Hardening Complete ==="
echo "SSH port: 2222 (key-only)"
echo "Firewall: enabled (SSH/HTTP/HTTPS)"
echo "fail2ban: active (bantime 1 hour)"
echo "auditd: active"
echo ""
echo "Next steps:"
echo "  1. Test SSH from a new terminal BEFORE closing this session"
echo "  2. Configure automatic security updates"
echo "  3. Set up log monitoring"

FAQ

Is changing the SSH port effective security?
It’s security by obscurity — it stops automated scans but not targeted attacks. Combine with key-only authentication and fail2ban for real security.
What’s the difference between UFW allow and limit?
allow permits unlimited connections. limit permits 6 connections per 30 seconds from a single IP, then blocks. Use limit for SSH and other sensitive services.
Should I use SELinux or AppArmor?
If you run Ubuntu/Debian, AppArmor is simpler and default. If you run RHEL/CentOS/Fedora, SELinux is built-in and well-integrated. Both provide similar protection.
How often should I review audit logs?
Daily automated review with alerts for critical events. Weekly manual review for pattern analysis. Integrate with a SIEM for real-time alerting.
Can fail2ban protect against DDoS?
No — fail2ban analyzes logs after the fact. It can’t handle high-volume DDoS. Use rate limiting at the load balancer, CDN, or kernel level for DDoS protection.
What’s the most important single hardening step?
Disabling password-based SSH authentication. It immediately eliminates the most exploited attack vector on public servers.

What’s Next

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-20.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro