Skip to content

Auditing iptables and nftables Rules with AI (and Verifying It)

Dump your ruleset, have AI flag shadowed, redundant and overly broad rules, then prove each finding with counters and nmap, including the IPv6 and Docker gaps.

●By 12 min read

Firewall rulesets grow by accretion. Someone opens a port for a migration, someone else adds a broader rule to fix a monitoring alert, and three years later nobody can say which of the 140 lines actually matter. Reading them top to bottom, tracking order and overlap in your head, is exactly the kind of tedious pattern-matching a model does well.

What a model cannot do is see the traffic. It reasons about the text of the rules, and the text is only part of the picture: Docker inserts its own chains, IPv6 has a separate table, and counters tell you which rules ever fire. Treat the AI output as a list of hypotheses, and verify each one against the running system before you touch anything.

Dump the Complete Ruleset

Give the model everything the kernel is enforcing, not just the config file you think is loaded. Collect all of it in one go:

# iptables (legacy or iptables-nft) β€” -c includes packet/byte counters

$ sudo iptables-save -c > /tmp/fw-v4.txt

$ sudo ip6tables-save -c > /tmp/fw-v6.txt

# Native nftables, with rule handles (-a) for precise references later

$ sudo nft -a list ruleset > /tmp/fw-nft.txt

# Front ends, if present β€” these describe intent, the dumps above describe reality

$ sudo ufw status verbose > /tmp/fw-ufw.txt

$ sudo firewall-cmd --list-all-zones > /tmp/fw-firewalld.txt

# What is actually listening, so the model can match rules to services

$ sudo ss -tulpn > /tmp/listening.txt

Collect both families β€” an audit that only looks at iptables-save misses ip6tables entirely. A locked-down IPv4 ruleset next to an empty IPv6 one with ACCEPT policies is one of the most common findings.

Collect both backends β€” on modern distros iptables is often iptables-nft, writing into nftables tables. Native nft rules can coexist with them, and a packet must pass every base chain on a hook. You need both dumps to see the whole path.

Sanitize what you share β€” rulesets reveal internal subnets, management IPs and service layout. Use a local model where possible β€” see running LLMs locally β€” or replace addresses consistently before pasting.

Ask for Specific Classes of Problem

"Is my firewall secure?" gets a generic checklist. Asking for named defect classes, with rule references, gets something you can verify line by line:

$ ollama run llama3.2 "Audit this Linux firewall ruleset. Rules are evaluated

top to bottom within a chain; the first matching ACCEPT, DROP or REJECT wins.

IPv4:

$(cat /tmp/fw-v4.txt)

IPv6:

$(cat /tmp/fw-v6.txt)

Listening services:

$(cat /tmp/listening.txt)

Report, each with the exact rule line(s) involved:

1. Default chain policies for INPUT, FORWARD, OUTPUT in both families

2. Shadowed rules: rules that can never match because an earlier rule

already handles all of their traffic

3. Redundant rules: duplicates or rules fully covered by another

4. Overly broad rules: 0.0.0.0/0 or ::/0 sources on admin ports,

port ranges, rules with no interface or source restriction

5. Listening services with no rule at all, and rules for ports nothing

listens on

6. Differences between the IPv4 and IPv6 policies

Do not propose a rewritten ruleset. If a finding depends on traffic

you cannot see, say so."

State the evaluation rule up front β€” models sometimes reason as if the most specific rule wins, like a routing table. Saying first-match-wins explicitly prevents a whole category of wrong shadowing claims.

Forbid the rewrite β€” a full replacement ruleset is unreviewable and almost always drops something subtle, like the loopback accept or ICMPv6 neighbour discovery. You want findings, then small targeted changes.

Counters are in the dump β€” with -c, each rule is prefixed by [packets:bytes]. Ask the model to flag rules with zero hits, but remember counters reset on reboot and on ufw reload.

What Shadowing and Breadth Look Like

A small example of the defects worth catching, in iptables-save format:

*filter

:INPUT DROP [0:0]

-A INPUT -i lo -j ACCEPT

-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

-A INPUT -p tcp --dport 22 -j ACCEPT # any source

-A INPUT -s 198.51.100.0/24 -p tcp --dport 22 -j ACCEPT # redundant: covered above

-A INPUT -s 203.0.113.50 -j DROP # shadowed for port 22

-A INPUT -p tcp --dport 3000:9000 -j ACCEPT # overly broad range

-A INPUT -p tcp --dport 5432 -j ACCEPT # database open to the world

COMMIT

The intent was clearly "SSH from the office, block that one abusive host"; the effect is SSH from everywhere, including the host you meant to block. The fix is ordering and narrowing, not more rules. Our firewall rules generator produces a clean baseline to compare against.

nftables has a different trap β€” an accept verdict only ends evaluation of the current base chain. If two base chains hook input β€” say an inet filter table and an ip filter table created by iptables-nft β€” a packet must be accepted by both, while a drop in either is final. Models frequently miss this.

Do not over-trust breadth findings β€” "0.0.0.0/0 on port 443" is correct for a public web server. The model does not know which services are meant to be public; you do.

sponsored

The Gaps AI Usually Misses

The most serious exposures are often not in the rules you wrote at all.

Docker bypasses INPUT β€” a port published with -p 8080:80 is DNATed in nat PREROUTING and then traverses FORWARD, not INPUT. Your INPUT rules, and ufw's, never see it. Restrictions belong in the DOCKER-USER chain, which Docker evaluates before its own FORWARD rules.

IPv6 left open β€” if ip6tables-save shows :INPUT ACCEPT with no rules and the host has a global IPv6 address, every listening service is reachable over IPv6 regardless of the IPv4 policy.

But do not drop all ICMPv6 β€” IPv6 depends on ICMPv6 for neighbour discovery and path MTU. A model told to tighten IPv6 will often suggest dropping ICMP entirely, which breaks connectivity in confusing ways.

Front end versus reality β€” ufw status shows what ufw manages. Rules added by Docker, fail2ban, Kubernetes or a hand-run iptables -I only show up in the raw dumps.

# Restrict a published container port to one source network.

# After DNAT the destination port is the container port, so match the

# original destination port with conntrack instead.

$ sudo iptables -I DOCKER-USER -i eth0 -p tcp -m conntrack \

--ctorigdstport 8080 --ctdir ORIGINAL ! -s 198.51.100.0/24 -j DROP

# Simpler, when only local access is needed: bind to loopback

$ docker run -p 127.0.0.1:8080:80 nginx

Verify Every Finding Before Changing Anything

Each claim is testable. Check it from outside, where an attacker sits, and from the counters, which record what the kernel actually matched:

# From a host OUTSIDE the network: what is really reachable?

$ nmap -Pn -p- --min-rate 1000 203.0.113.10

$ nmap -6 -Pn -p- --min-rate 1000 2001:db8::10

$ sudo nmap -sU --top-ports 50 203.0.113.10

# On the server: does a supposedly shadowed rule ever match?

$ sudo iptables -L INPUT -n -v --line-numbers

$ sudo nft list chain inet filter input

# Zero the counters, generate test traffic, then look again

$ sudo iptables -Z INPUT

FindingHow to confirmWatch out for
Shadowed ruleCounter stays at 0 while matching traffic is sentCounters reset on reload and reboot
Overly broad sourcenmap from an unrelated network connectsCloud security groups may block it upstream
IPv6 gapnmap -6 shows ports closed on IPv4 as openHost needs a reachable global address
Docker exposurePort reachable despite INPUT dropTest from outside, not from the host itself
Redundant ruleRemoving it in staging changes no test resultRule may exist for a future change

Scan from the outside β€” scanning a host from itself goes through loopback and tells you almost nothing about external exposure. Use a VPS or a second network.

Upstream filters hide problems β€” a cloud security group may currently block what the host firewall allows. That is luck, not defense in depth β€” fix the host rule anyway.

Change Safely With a Timed Rollback

A wrong firewall change on a remote host can lock you out. Before applying anything, schedule the revert, then cancel it once you have confirmed you can still connect from a fresh session:

# iptables: back up, schedule a revert in 5 minutes, then change

$ sudo sh -c 'iptables-save > /root/fw-v4.bak'

$ echo 'iptables-restore < /root/fw-v4.bak' | sudo at now + 5 minutes

# Remove the any-source SSH rule; the office-only rule below it now applies

$ sudo iptables -D INPUT -p tcp --dport 22 -j ACCEPT

# From a NEW ssh session, confirm access, then cancel the revert

$ sudo atq

$ sudo atrm <job-number>

# iptables-apply does the same interactively and reverts on timeout

$ sudo iptables-apply -t 60 /etc/iptables/rules.v4

# nftables: check syntax first, keep an atomic restorable backup

$ sudo nft -c -f /etc/nftables.conf

$ sudo sh -c "{ echo 'flush ruleset'; nft list ruleset; } > /root/nft.bak"

$ echo 'nft -f /root/nft.bak' | sudo at now + 5 minutes

$ sudo nft -f /etc/nftables.conf

Test from a new connection β€” your existing SSH session survives on the ESTABLISHED rule even when new connections are blocked. Only a fresh login proves the change is safe.

Persist only after verifying β€” live changes vanish on reboot until saved with netfilter-persistent save, iptables-save into the distro's rules file, or an updated /etc/nftables.conf. Save last, not first.

Change one finding at a time β€” small changes make the counters and nmap results attributable. See our hardening guide and SSH security practices for the wider baseline.

Frequently Asked Questions

Can AI audit my iptables or nftables rules?

It is good at reading a full ruleset and spotting shadowed, redundant and overly broad rules, and at comparing IPv4 against IPv6 policy. It cannot see traffic or know which services are meant to be public, so treat each finding as a hypothesis and confirm it with counters and an external nmap scan.

What is a shadowed firewall rule?

A rule that can never match because an earlier rule in the same chain already accepts or drops all of its traffic. Since iptables uses first-match-wins, a specific block placed after a broad accept has no effect. Its packet counter staying at zero under matching traffic confirms it.

Why does Docker ignore my ufw or INPUT rules?

Published container ports are DNATed and routed through the FORWARD chain rather than INPUT, so host INPUT rules and ufw never evaluate that traffic. Put restrictions in the DOCKER-USER chain, matching the original port with conntrack, or bind the published port to 127.0.0.1.

Do I need separate IPv6 firewall rules?

Yes, unless you use an nftables inet table that covers both families. iptables only filters IPv4; ip6tables is a separate ruleset with its own default policies. Allow ICMPv6 for neighbour discovery rather than dropping it, or IPv6 connectivity breaks.

How do I avoid locking myself out when changing firewall rules?

Back up the current ruleset, schedule an automatic restore with at, apply the change, then confirm access from a brand-new SSH session before cancelling the restore. iptables-apply automates this pattern by reverting unless you confirm within a timeout.

sponsored

Related Tools

See also