Skip to main content

UFW & Fail2ban (Network Protection and Attack Mitigation)

A systemic tandem of the UFW (Uncomplicated Firewall) packet filtering utility and the Fail2ban daemon, which analyzes system logs in real-time and dynamically blocks the IP addresses of malicious actors.

1. Concept Overview & Systemic Problem

A publicly accessible Linux server on the internet is subject to continuous automated scanning by botnets. Scanners look for open ports, weak SSH passwords, exposed database ports (PostgreSQL, Redis, MySQL), and unsecured admin panels. Without a systemic network firewall, the server becomes vulnerable to Denial of Service (DoS) attacks, exhaustion of open socket descriptor limits, and direct unauthorized access.

UFW (Uncomplicated Firewall) and Fail2ban form a two-tiered active protection system:

  • UFW (Static Level): A simplified configuration interface for the Linux kernel's iptables / nftables subsystem based on Default-Deny: all incoming connections are blocked except for explicitly allowed ports (SSH, HTTP/HTTPS).
  • Fail2ban (Dynamic Level): A behavioral analysis daemon that monitors system logs and dynamically modifies firewall rules, isolating bot addresses that exhibit patterns of brute-force attacks or vulnerability probing.
+-------------------------------------------------------------------+
| Incoming Traffic (Internet)                                        |
+---------------------------------+---------------------------------+
                                 |
                                 v
                   +-----------------------------+
                   |       Fail2ban Daemon       |
                   | (Log Analysis journald/auth)|
                   +--------------+--------------+
                                 | Dynamic DROP (e.g., 24 hours)
                                 v
                   +-----------------------------+
                   |        UFW / Netfilter      |
                   |  (iptables / nftables kernel)|
                   +--------------+--------------+
                                 |
              +-------------------+-------------------+
              | Allowed (22, 80, 443)                | Blocked (DROP)
              v                                       v
    +-------------------+                   +-------------------+
    | System Services    |                   | Packet Dropped    |
    | (Nginx, SSHd)     |                   | without response   |
    +-------------------+                   +-------------------+

2. Architectural Taxonomy & Mental Model

Node protection is based on a clear distribution of responsibilities among components:

  1. Kernel Network Filter (Netfilter / nftables):
    • A low-level engine in the Linux kernel that checks the headers of each network packet on network interfaces.
  2. UFW (Uncomplicated Firewall):
    • A utility for simple management of the INPUT, OUTPUT, FORWARD filtering chains.
    • Provides human-readable syntax instead of complex iptables syntax constructs.
    • Enforces a static policy: blocking unauthorized port ranges.
  3. Fail2ban (Infiltration Prevention Framework):
    • Jail: A configuration entity that links a filter (filter.d), log file, and blocking action (action.d).
    • Filter: A set of regular expressions (failregex) for parsing authentication failures.
    • Action: An instruction (e.g., iptables-multiport) that temporarily adds an IP to the packet rejection chain.

3. Technical Pipeline & Internal Mechanics

Basic Deployment and Configuration of UFW

[!IMPORTANT] Always allow the SSH port before enabling the firewall to avoid locking yourself out:

# 1. Reset and default policies (deny all incoming, allow outgoing)
sudo ufw default deny incoming
sudo ufw default allow outgoing

# 2. Allow critical ports
sudo ufw allow 22/tcp comment "OpenSSH"
sudo ufw allow 80/tcp comment "HTTP (Let's Encrypt)"
sudo ufw allow 443/tcp comment "HTTPS"

# 3. Enable the firewall
sudo ufw --force enable
sudo ufw status verbose

Fail2ban Configuration (/etc/fail2ban/jail.local)

Never edit jail.conf directly (it gets overwritten by package updates); create an overrides file:

[DEFAULT]
# List of trusted IPs (whitelist that is never blocked)
ignoreip = 127.0.0.1/8 ::1 198.51.100.12

# Ban time (1 day) and observation window (10 minutes)
bantime  = 24h
findtime = 10m
maxretry = 5

# Use systemd backend for modern distributions (Ubuntu 22.04/24.04, Debian 12)
backend = systemd
banaction = ufw

[sshd]
enabled = true
port    = 22
mode    = aggressive

[nginx-http-auth]
enabled = true
port    = http,https
logpath = /var/log/nginx/error.log

Managing the daemon:

sudo systemctl enable --now fail2ban
# Check the status of the SSH jail
sudo fail2ban-client status sshd

4. Production Engineering Scenarios

01. Protection Against Targeted Vulnerability Scanning of Nginx / Web Server

Malicious actors run scanners (nikto, sqlmap) that generate hundreds of requests to /wp-login.php, /.env, /phpmyadmin. A custom filter /etc/fail2ban/filter.d/nginx-botsearch.conf is created to track 404 and 403 status codes in the logs for such paths. After 3 attempts, the IP is blocked for 7 days at the firewall level, preserving the instance's CPU.

02. Restricting Access to Internal Prometheus Metrics and Database Services

A monitoring server or PostgreSQL database needs to listen on an external interface for replication or metric scraping. Instead of publicly exposing ports, restrictions are set for specific IP addresses of partner nodes:

sudo ufw allow from 10.0.0.15 to any port 9090 proto tcp comment "Prometheus scraper"
sudo ufw allow from 10.0.0.20 to any port 5432 proto tcp comment "Postgres Replica"

All other requests to these ports are dropped without sending TCP RST (stealth mode).

03. Emergency Unblocking via fail2ban-client

A developer mistakenly connects with the wrong key, causing their home IP to be banned. The administrator connects through a Jump host and unblocks without restarting the entire service:

sudo fail2ban-client set sshd unbanip 203.0.113.45

5. Pitfalls, Common Mistakes & Security

  1. Docker bypasses UFW via iptables: When Docker runs a container with the -p 8080:8080 option, it creates rules in the PREROUTING chain that take precedence over UFW rules. The port opens to the world, even if ufw status shows a block. Fix this by binding ports exclusively to loopback (-p 127.0.0.1:8080:8080) or disabling iptables management in /etc/docker/daemon.json ({"iptables": false}).
  2. Log overflow without logrotate: If Fail2ban parses gigabyte-sized web server logs without rotation, the daemon will start consuming 100% CPU trying to read the file from the beginning. Ensure that logrotate correctly compresses log files.
  3. Blocking own proxies (Cloudflare / Reverse Proxy): If the web server operates behind Cloudflare or AWS ALB, Fail2ban in its default configuration will block Cloudflare's IP addresses instead of the real clients. Configure the mod_remoteip module in Nginx/Apache to restore X-Forwarded-For before activating HTTP jails.
/ Frequently Asked QuestionsSchema.org FAQPage

FAQ: UFW & Fail2ban (Network Protection and Attack Mitigation)

By default, the Docker daemon manipulates iptables tables directly (via the DOCKER chain in PREROUTING), bypassing UFW's filtering chains (ufw-user-input). This means that if you publish a container port using -p 5432:5432, the database becomes open to the entire internet, even if UFW has an active rule blocking that port. To resolve this issue, bind to the local interface (-p 127.0.0.1:5432:5432) or use utilities like ufw-docker.
/ Internal links
All terms