Linux firewall configuration skill: UFW (Uncomplicated Firewall), iptables, nftables fundamentals, and fail2ban intrusion prevention. USE WHEN: - Setting up UFW on a new server (default deny-in, allow-out, selective port opens) - Configuring rate limiting and per-IP connection limits with UFW or iptables - Installing and tuning fail2ban for SSH, Nginx, and custom application jails - Writing iptables NAT rules (MASQUERADE for WireGuard/VPN, DNAT for port forwarding) - Diagnosing lockouts, fail2ban false positives, or blocked legitimate traffic - Setting up cloud security groups as the first layer of defence DO NOT USE FOR: - Application-level authentication / authorisation (use authentication skill) - SSL/TLS certificate management (use ssl-tls skill) - Kubernetes NetworkPolicy rules (use kubernetes skill) - WAF (Web Application Firewall) rules — Nginx rate limiting covers basic cases

GitHub
安装命令
npx skhub add claude-dev-suite/firewall
Markdown
SKILL.md

Firewall Configuration — UFW, iptables, nftables, fail2ban

Defence-in-Depth Model

A production server should have at least two layers:

  1. Cloud security group — controls traffic at the hypervisor/VPC level; blocks reach the host NIC. Cheapest filter; stateful by default. Configure this first.
  2. Host-based firewall (UFW / iptables / nftables) — applied by the kernel; catches port scans, limits rate of connections, enforces policy if security group is misconfigured.
  3. fail2ban — bans IP addresses that show malicious behaviour patterns (too many auth failures, suspicious request patterns) by inserting iptables rules.

UFW — Full Server Lockdown Sequence

Run this sequence on a fresh server before exposing it to the internet. Keep your current SSH session open while testing — UFW changes take effect immediately.

# 1. Install UFW (usually pre-installed on Ubuntu)
sudo apt-get install -y ufw

# 2. Set default policies — deny all inbound, allow all outbound
sudo ufw default deny incoming
sudo ufw default allow outgoing

# 3. Allow SSH BEFORE enabling UFW (otherwise you lock yourself out)
sudo ufw allow 22/tcp comment 'SSH'
# Or limit SSH to your own IP range:
sudo ufw allow from 203.0.113.0/24 to any port 22 proto tcp comment 'SSH from office'

# 4. Allow web traffic
sudo ufw allow 80/tcp  comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'

# 5. Allow other application ports as needed
sudo ufw allow 5432/tcp comment 'PostgreSQL — app subnet only'
# Better: restrict by source IP
sudo ufw allow from 10.0.1.0/24 to any port 5432 proto tcp comment 'PostgreSQL internal'

# 6. Enable UFW (prompts for confirmation)
sudo ufw enable

# 7. Verify status
sudo ufw status verbose
sudo ufw status numbered   # Shows rule numbers for easier deletion

UFW Common Operations

# Remove a rule by number (get numbers from 'ufw status numbered')
sudo ufw delete 5

# Remove a rule by specification (exact match)
sudo ufw delete allow 80/tcp

# Rate-limit a port (UFW built-in: block IPs that connect > 6 times in 30s)
sudo ufw limit 22/tcp comment 'Rate-limit SSH brute force'

# Allow by named service (reads /etc/services)
sudo ufw allow smtp
sudo ufw allow 'Nginx Full'    # Application profile from /etc/ufw/applications.d/

# List available application profiles
sudo ufw app list
sudo ufw app info 'Nginx Full'

# Logging levels: off, low, medium, high, full
sudo ufw logging medium

# Reload (re-read rules without disabling)
sudo ufw reload

# Disable UFW completely
sudo ufw disable

# Reset to defaults (removes all rules)
sudo ufw reset

# Check UFW log
tail -f /var/log/ufw.log
journalctl -k | grep UFW

iptables Fundamentals

UFW is a frontend to iptables. For advanced use cases (NAT, custom chains), work directly with iptables.

Chains and Tables

TableChainsPurpose
filter (default)INPUT, FORWARD, OUTPUTAccept / drop / reject packets
natPREROUTING, POSTROUTING, OUTPUTAddress and port translation
mangleAll five chainsModify packet headers (TTL, QoS marks)
rawPREROUTING, OUTPUTBypass connection tracking

Common iptables Commands

# List all rules with line numbers and counters
sudo iptables -L -n -v --line-numbers
sudo iptables -t nat -L -n -v          # NAT table

# Allow established/related connections (essential — add before deny rules)
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -i lo -j ACCEPT                    # Allow loopback

# Allow specific port
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# Block a specific IP
sudo iptables -I INPUT -s 192.0.2.100 -j DROP

# Reject with ICMP (polite; informs sender)
sudo iptables -A INPUT -p tcp --dport 25 -j REJECT --reject-with tcp-reset

# Limit connection rate (SYN flood protection)
sudo iptables -A INPUT -p tcp --syn --dport 80 \
    -m connlimit --connlimit-above 20 -j REJECT

# Save rules persistently (package: iptables-persistent)
sudo apt-get install -y iptables-persistent
sudo netfilter-persistent save       # Saves to /etc/iptables/rules.v4 and rules.v6

# Restore saved rules
sudo netfilter-persistent reload

# Flush all rules (dangerous — default policy must be ACCEPT first)
sudo iptables -P INPUT ACCEPT
sudo iptables -F
sudo iptables -X

NAT — MASQUERADE (WireGuard / VPN Gateway)

# Enable IP forwarding (also set in sysctl.d)
echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/50-ip-forward.conf
sudo sysctl -p /etc/sysctl.d/50-ip-forward.conf

# Masquerade VPN traffic going out via eth0
# (wg0 = WireGuard interface; eth0 = public internet interface)
sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE

# Allow forwarded traffic from VPN
sudo iptables -A FORWARD -i wg0 -j ACCEPT
sudo iptables -A FORWARD -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

sudo netfilter-persistent save

NAT — DNAT (Port Forwarding)

# Forward external :8080 to internal host 10.0.1.10:80
sudo iptables -t nat -A PREROUTING -p tcp --dport 8080 \
    -j DNAT --to-destination 10.0.1.10:80
sudo iptables -A FORWARD -p tcp -d 10.0.1.10 --dport 80 \
    -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT

nftables Basics

nftables is the successor to iptables (used by default on Debian 11+ and Ubuntu 22.04+). UFW still uses iptables-legacy by default on Ubuntu.

# Check if nftables is in use
sudo nft list ruleset

# Basic nftables configuration file: /etc/nftables.conf
cat > /etc/nftables.conf <<'NFTABLES'
#!/usr/sbin/nft -f
flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;

        iif lo accept comment "Allow loopback"
        ct state established,related accept comment "Allow established connections"
        ct state invalid drop comment "Drop invalid packets"

        tcp dport 22 accept comment "SSH"
        tcp dport { 80, 443 } accept comment "HTTP/HTTPS"

        # ICMP (allow ping)
        ip  protocol icmp   icmp type echo-request accept
        ip6 nexthdr  icmpv6 accept

        log prefix "nft-drop: " flags all counter drop
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}
NFTABLES

sudo systemctl enable --now nftables
sudo nft -f /etc/nftables.conf   # Apply immediately

fail2ban — Complete Configuration

Install

sudo apt-get install -y fail2ban
# Copy default config so updates don't overwrite your changes
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

/etc/fail2ban/jail.local — Full Example

[DEFAULT]
# Global defaults (inherited by all jails unless overridden)
bantime   = 1h            # How long to ban (increase to 24h or 7d for repeat offenders)
findtime  = 10m           # Window in which maxretry failures trigger a ban
maxretry  = 5             # Number of failures before ban
ignoreip  = 127.0.0.1/8 ::1 10.0.0.0/8 203.0.113.0/24
            # ^ Whitelist: loopback, private network, office IP range
banaction = iptables-multiport
banaction_allports = iptables-allports
action    = %(action_mwl)s  # Ban + log + send email with whois/log context
# For email (requires mailutils): set destemail and sender
destemail = admin@example.com
sender    = fail2ban@example.com
mta       = sendmail

# --- SSH -------------------------------------------------------------------
[sshd]
enabled  = true
port     = ssh
filter   = sshd
logpath  = %(sshd_log)s
backend  = %(sshd_backend)s
maxretry = 3
bantime  = 24h

# --- Nginx: too many requests (from rate-limit log entries) ----------------
[nginx-req-limit]
enabled  = true
filter   = nginx-req-limit
port     = http,https
logpath  = /var/log/nginx/myapp.error.log
maxretry = 10
findtime = 1m
bantime  = 1h

# --- Nginx: authentication failures ----------------------------------------
[nginx-http-auth]
enabled  = true
filter   = nginx-http-auth
port     = http,https
logpath  = /var/log/nginx/myapp.error.log
maxretry = 5
bantime  = 6h

# --- Nginx: bad bots / scanners (4xx flooding) -----------------------------
[nginx-badbots]
enabled  = true
filter   = nginx-badbots
port     = http,https
logpath  = /var/log/nginx/myapp.access.log
maxretry = 2
bantime  = 24h

Custom Filter — /etc/fail2ban/filter.d/nginx-req-limit.conf

[Definition]
# Matches Nginx error log lines produced by limit_req:
# 2024/01/15 12:34:56 [error] 12345#0: *1234 limiting requests, excess: 20.123
#                             by zone "api", client: 1.2.3.4, server: ...
failregex = ^\S+ \S+ \[error\] \d+#\d+: \*\d+ limiting requests, .+, client: <HOST>,
datepattern = %%Y/%%m/%%d %%H:%%M:%%S

[Init]
maxlines = 1

Custom Filter — /etc/fail2ban/filter.d/nginx-http-auth.conf

[Definition]
# Matches Nginx error log lines for failed HTTP Basic auth
failregex = ^\S+ \S+ \[error\] \d+#\d+: \*\d+ no user/password was provided for basic authentication.+, client: <HOST>,
            ^\S+ \S+ \[error\] \d+#\d+: \*\d+ user "\S+" was not found in.+, client: <HOST>,
            ^\S+ \S+ \[error\] \d+#\d+: \*\d+ user "\S+": password mismatch.+, client: <HOST>,
datepattern = %%Y/%%m/%%d %%H:%%M:%%S

fail2ban Management Commands

# Start / enable
sudo systemctl enable --now fail2ban

# Overall status
sudo fail2ban-client status

# Status of a specific jail
sudo fail2ban-client status sshd

# Manually ban an IP in a jail
sudo fail2ban-client set sshd banip 198.51.100.42

# Unban an IP from a specific jail
sudo fail2ban-client set sshd unbanip 198.51.100.42

# Unban from all jails
sudo fail2ban-client unban 198.51.100.42

# Reload fail2ban config (picks up jail.local changes)
sudo fail2ban-client reload

# Test a filter against a log file (invaluable for debugging regexes)
sudo fail2ban-regex \
    /var/log/nginx/myapp.error.log \
    /etc/fail2ban/filter.d/nginx-req-limit.conf \
    --print-all-matched

# Check what fail2ban has added to iptables
sudo iptables -L f2b-sshd -n -v

Anti-Patterns

Anti-patternWhy it's harmfulFix
ufw allow from any to any or allowing 0.0.0.0/0 on all portsEffectively disables the firewall; any service binds to a port is reachableDefault deny incoming; allow only specific ports and source ranges
Not adding SSH allow rule before ufw enableLocks yourself out immediately; requires console/KVM accessAlways run ufw allow 22/tcp before ufw enable
No fail2ban on SSH portSSH brute-force attempts run 24/7; eventually succeed with weak keys or if password auth is accidentally enabledInstall and enable the [sshd] fail2ban jail on every public server
fail2ban ignoreip not setYour own IP could be banned during testing or after a scripting errorAlways whitelist your management IP range in ignoreip
bantime = 10m (default)Too short; determined attackers rotate through bans quicklySet bantime = 1h minimum; use bantime.increment = true for repeat offenders
UFW disabled on cloud instances because "security groups are enough"Security group misconfiguration or lateral movement from compromised instance on same SG bypasses cloud-level rulesRun both; host firewall is a safety net
Saving iptables rules with a script in /etc/rc.localrc.local runs late in boot; firewall gap during startup; rules lost on iptables -FUse iptables-persistent / netfilter-persistent; rules restored at network-up
nftables and iptables active simultaneouslyPackets can match both rulesets; confusing interaction; double-countingChoose one: either use UFW (iptables-nft backend) or native nftables, not both
fail2ban filter regex not tested before deploymentTypos cause the filter to never match — jail silently does nothingAlways run fail2ban-regex <logfile> <filterfile> before enabling a jail
Public-facing DB port (5432, 3306, 27017) open to 0.0.0.0/0Database exposed to the internet; brute-forced or default credentials exploitedAllow DB ports from application subnet IP range only

Troubleshooting

SymptomLikely causeDiagnostic / Fix
Locked out after ufw enableSSH rule not added before enablingRecover via cloud console / out-of-band access; ufw allow 22 and ufw enable again
fail2ban not banning despite log matchesFilter regex doesn't match log format, or logpath is wrongRun fail2ban-regex <logfile> <filterfile>; verify logpath glob matches actual log files
Legitimate users getting banned (false positives)maxretry too low, findtime too short, or shared NAT IPAdd their IP/range to ignoreip; raise maxretry; lengthen findtime
Banned IP still connectingBan inserted into iptables but not the right chain; or nftables in use while fail2ban uses iptablessudo iptables -L f2b-sshd -n; switch fail2ban banaction to nftables-multiport if on nftables
fail2ban service fails to startSyntax error in jail.local or filter filesudo fail2ban-client -x start; journalctl -u fail2ban -n 50
Port is open despite UFW deny ruleRule order matters; first matching rule wins; allow rule created before denyufw status numbered; delete the allow rule if deny should take precedence
iptables rules lost after rebootNot persisted with netfilter-persistent savesudo netfilter-persistent save; verify /etc/iptables/rules.v4
UFW logging fills diskLogging level set to full on high-traffic serverSet ufw logging low; add limit_req_log_level warn in Nginx to reduce blocked request logging
WireGuard clients can't reach internet after NAT setupIP forwarding disabled, or MASQUERADE rule on wrong interfacesysctl net.ipv4.ip_forward; verify interface name with ip addr; recheck iptables -t nat -L POSTROUTING -n -v
发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

Sep 24, 2026

分类

未分类

许可证

MIT

源路径

skills/infrastructure/firewall

默认分支

main

最新提交

9496306

Tree SHA

fe4e2f1