Secure your Linux servers with professional firewall configuration and SSH hardening. This DevOps guide covers UFW, iptables, fail2ban, and essential security practices to protect your infrastructure from attacks.
Why Security Hardening Matters
Unsecured SSH and open ports are the #1 attack vector for Linux servers. Automated bots scan the internet 24/7 for vulnerabilities. Proper hardening reduces your attack surface by 90%.
Essential Security Tools
UFW (Uncomplicated Firewall) - Simple frontend for iptables
iptables - Advanced packet filtering
nftables - Modern replacement for iptables
sshd_config - SSH server configuration
ssh-keygen - Key pair generation
ssh-copy-id - Key deployment
fail2ban - Block brute force attacks
crowdsec - Modern IPS alternative
logwatch - Log analysis
Step 1: Firewall Setup with UFW
Basic UFW Configuration
# Install UFW (if not installed)
sudo apt update
sudo apt install ufw -y
# Check UFW status
sudo ufw status
sudo ufw status verbose
# Default policies: DENY incoming, ALLOW outgoing
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH (essential - do this BEFORE enabling!)
sudo ufw allow ssh
# OR specify custom port
sudo ufw allow 2222/tcp
# Allow web ports
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Allow specific IP addresses
sudo ufw allow from 192.168.1.100
sudo ufw allow from 192.168.1.0/24
# Enable UFW (CAREFUL: ensure SSH is allowed!)
sudo ufw enable
# Disable UFW (if locked out)
sudo ufw disable
Advanced UFW Rules
# Allow port range
sudo ufw allow 3000:4000/tcp
# Allow specific protocol
sudo ufw allow 53/udp # DNS
# Rate limiting (prevents brute force)
sudo ufw limit ssh
sudo ufw limit 2222/tcp
# Delete rules
sudo ufw status numbered # List with numbers
sudo ufw delete 2 # Delete rule #2
# Deny specific IP
sudo ufw deny from 203.0.113.10
# Application profiles
sudo ufw app list
sudo ufw allow 'Nginx Full'
sudo ufw allow 'OpenSSH'
# Logging
sudo ufw logging on
sudo ufw logging high
# Show rules with numbers
sudo ufw status numbered
# Backup UFW rules
sudo ufw show added > ufw-backup.txt
# Restore
sudo ufw reset
cat ufw-backup.txt | sudo ufw
Step 2: SSH Hardening
Essential sshd_config Changes
# Backup original config
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
# Edit SSH configuration
sudo nano /etc/ssh/sshd_config
# === SECURE SETTINGS ===
# Change SSH port (pick between 1024-65535)
Port 2222
# Disable root login
PermitRootLogin no
# Limit user access
AllowUsers alice bob charlie
# OR use groups
AllowGroups ssh-users
# Use key authentication only
PasswordAuthentication no
ChallengeResponseAuthentication no
# Strong encryption algorithms
KexAlgorithms curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# Connection settings
ClientAliveInterval 300
ClientAliveCountMax 2
MaxAuthTries 3
MaxSessions 10
# Restrict SFTP users (if needed)
Match Group sftp-only
ChrootDirectory /home/%u
ForceCommand internal-sftp
AllowTcpForwarding no
# Reload SSH (don't restart - test first!)
sudo systemctl reload ssh
# If locked out, use console or VNC to revert changes
ssh -p 2222 user@server
SSH Key Management
# Generate SSH key pair (on your local machine)
ssh-keygen -t ed25519 -C "your_email@example.com"
# Or for legacy systems
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
# Copy public key to server
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server -p 2222
# On server, secure the authorized_keys file
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
# Add extra security to authorized_keys
# Edit ~/.ssh/authorized_keys and add restrictions:
from="192.168.1.*",command="/bin/true",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAAC3...
# SSH agent for passphrase management
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# Test connection without password
ssh -p 2222 user@server
Step 3: Install & Configure fail2ban
# Install fail2ban
sudo apt update
sudo apt install fail2ban -y
# Copy configuration file
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
# Edit main configuration
sudo nano /etc/fail2ban/jail.local
# === IMPORTANT SETTINGS ===
[DEFAULT]
# Ban IP for 1 hour
bantime = 3600
# Maximum retries before ban
maxretry = 5
# Time window for retries
findtime = 600
# Whitelist your IP
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24
# SSH jail (custom port)
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
# Nginx jail
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
# Custom jail for specific attacks
[my-custom-jail]
enabled = true
filter = myfilter
logpath = /var/log/myapp.log
maxretry = 3
bantime = 86400
# Start and enable fail2ban
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
DevOps Comparison: Firewall Tools
| Tool | Best For | Complexity | DevOps Use Case |
|---|---|---|---|
| UFW | Quick setup, simple rules | LOW | Development servers, small projects |
| iptables | Full control, complex rules | HIGH | Production, network gateways |
| firewalld | Dynamic rules, zones | MEDIUM | RHEL/CentOS servers |
| nftables | Modern, high performance | HIGH | New deployments, high traffic |
Advanced iptables Configuration
# Save current rules
sudo iptables-save > iptables-backup.txt
# Basic iptables script
#!/bin/bash
# Flush all rules
iptables -F
iptables -X
# Default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow localhost
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# Allow established connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow SSH (custom port)
iptables -A INPUT -p tcp --dport 2222 -j ACCEPT
# Allow web ports
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Rate limiting for SSH
iptables -A INPUT -p tcp --dport 2222 -m state --state NEW -m recent --set
iptables -A INPUT -p tcp --dport 2222 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
# ICMP (ping) - allow but limit
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/second -j ACCEPT
# Save rules permanently
sudo apt install iptables-persistent
sudo netfilter-persistent save
# View rules
sudo iptables -L -v -n
sudo iptables -L -v -n --line-numbers
Complete Security Checklist
Automation Scripts for DevOps
1. Automated Server Hardening Script
#!/bin/bash
# auto-harden.sh - Automated server hardening
set -e
echo "=== Starting Server Hardening ==="
# Backup everything first
BACKUP_DIR="/root/backups/$(date +%Y%m%d_%H%M%S)"
mkdir -p $BACKUP_DIR
cp /etc/ssh/sshd_config $BACKUP_DIR/
cp /etc/ufw/user.rules $BACKUP_DIR/ 2>/dev/null || true
# 1. Update system
apt update && apt upgrade -y
# 2. Configure UFW
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow 2222/tcp comment 'SSH Custom Port'
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
ufw --force enable
# 3. Harden SSH
sed -i 's/^#Port 22/Port 2222/' /etc/ssh/sshd_config
sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^ChallengeResponseAuthentication yes/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
echo "AllowUsers $(whoami)" >> /etc/ssh/sshd_config
# 4. Install fail2ban
apt install -y fail2ban
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
cat >> /etc/fail2ban/jail.local << EOF
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
EOF
systemctl restart fail2ban
# 5. Set secure kernel parameters
echo "net.ipv4.tcp_syncookies = 1" >> /etc/sysctl.conf
echo "net.ipv4.conf.all.rp_filter = 1" >> /etc/sysctl.conf
echo "net.ipv4.conf.default.rp_filter = 1" >> /etc/sysctl.conf
sysctl -p
echo "=== Hardening Complete ==="
echo "IMPORTANT: Test SSH on port 2222 before closing this session!"
echo "Command: ssh -p 2222 $(whoami)@$(hostname -I | awk '{print $1}')"
2. SSH Security Audit Script
#!/bin/bash
# ssh-audit.sh - Check SSH security configuration
echo "=== SSH Security Audit ==="
echo "Date: $(date)"
echo ""
# 1. Check SSH port
echo "1. SSH Port:"
ss -tlnp | grep sshd || echo "SSH not running!"
# 2. Check sshd_config
echo -e "\n2. SSH Configuration:"
echo "Root Login: $(grep -i 'PermitRootLogin' /etc/ssh/sshd_config | tail -1)"
echo "Password Auth: $(grep -i 'PasswordAuthentication' /etc/ssh/sshd_config | tail -1)"
echo "Max Auth Tries: $(grep -i 'MaxAuthTries' /etc/ssh/sshd_config | tail -1)"
# 3. Check authorized_keys permissions
echo -e "\n3. SSH Key Permissions:"
for user in /home/*; do
if [ -f "$user/.ssh/authorized_keys" ]; then
perms=$(stat -c "%a" "$user/.ssh/authorized_keys" 2>/dev/null || echo "N/A")
echo " $user: $perms"
fi
done
# 4. Check failed login attempts
echo -e "\n4. Failed Login Attempts (last 24h):"
grep "Failed password" /var/log/auth.log | grep "$(date +%Y-%m-%d)" | wc -l
# 5. Check connected SSH sessions
echo -e "\n5. Current SSH Sessions:"
who | grep -E "pts/|ssh"
# 6. Check for weak SSH keys
echo -e "\n6. SSH Key Types:"
for key in /etc/ssh/ssh_host_*_key.pub; do
if [ -f "$key" ]; then
type=$(ssh-keygen -l -f "$key" | awk '{print $4}')
echo " $key: $type"
fi
done
echo -e "\n=== Audit Complete ==="
Troubleshooting Common Issues
Problem: Locked out after SSH changes
# Solution 1: Use console/VNC access
# 1. Log in via cloud console or VNC
# 2. Restore backup config
sudo cp /etc/ssh/sshd_config.backup /etc/ssh/sshd_config
sudo systemctl restart ssh
# Solution 2: If UFW blocked SSH
sudo ufw disable
sudo ufw allow ssh
sudo ufw enable
# Solution 3: Check iptables
sudo iptables -F
sudo iptables -P INPUT ACCEPT
sudo systemctl restart ssh
Problem: fail2ban not blocking attacks
# Check fail2ban status
sudo fail2ban-client status
sudo fail2ban-client status sshd
# Check logs
sudo tail -f /var/log/fail2ban.log
# Test filter
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
# Manual ban
sudo fail2ban-client set sshd banip 192.168.1.100
# Restart fail2ban
sudo systemctl restart fail2ban
Monitoring & Maintenance
# Daily security checks
# 1. Check failed SSH attempts
sudo grep "Failed password" /var/log/auth.log | tail -20
# 2. Check fail2ban bans
sudo fail2ban-client status sshd
# 3. Check open ports
sudo netstat -tulpn | grep LISTEN
sudo ss -tulpn
# 4. Check UFW status
sudo ufw status verbose
# 5. Check connected SSH users
sudo who
sudo w
# 6. Monitor real-time attacks
sudo tail -f /var/log/auth.log | grep -E "Failed|Invalid"
# 7. Check for root login attempts
sudo grep "root" /var/log/auth.log | grep "Failed"
# 8. Weekly: Update SSH keys
# Remove unused keys from authorized_keys
# Rotate host keys (optional)
sudo rm /etc/ssh/ssh_host_*
sudo dpkg-reconfigure openssh-server
1. Always test changes in staging first
2. Keep backup SSH sessions open during changes
3. Document all firewall and SSH rules
4. Regularly audit and remove unused rules
5. Use Infrastructure as Code (Ansible/Terraform) for consistency
6. Implement automated security scanning
7. Set up alerts for failed login attempts
8. Regularly rotate SSH keys (every 6-12 months)
DevOps Security Mindset
Security is not a one-time setup but an ongoing process. A properly hardened server reduces attack surface by 90% and prevents most automated attacks.
Remember: Default configurations are insecure by design. Always assume your servers will be attacked and build defenses accordingly.
Next Steps: Implement centralized logging (ELK stack), set up intrusion detection systems (OSSEC), and automate compliance scanning with tools like Lynis or OpenSCAP.
• UFW status:
sudo ufw status verbose• Test SSH:
ssh -p 2222 user@server• Check banned IPs:
sudo fail2ban-client status sshd• View open ports:
sudo ss -tulpn• Monitor attacks:
sudo tail -f /var/log/auth.log• Backup SSH config:
sudo cp /etc/ssh/sshd_config{,.backup}