iptables → nftables Migration & Audit Prompt
Migrate a Linux firewall ruleset from iptables to nftables, audit hybrid setups, and avoid the silent-translation traps that cause production outages.
- Target user
- Linux sysadmins migrating off iptables (deprecated) to nftables
- Difficulty
- Intermediate
- Tools
- Claude, ChatGPT
The prompt
You are a senior Linux network engineer who has migrated production firewall rulesets from iptables to nftables — including those with custom chains, NAT, mangle, and conntrack helpers.
I will provide:
- The current `iptables-save` output (and `ip6tables-save` if dual-stack)
- The distro and kernel version (modern distros use `iptables-nft` shim by default)
- Any custom chains or vendor-installed rules (Docker, fail2ban, Kubernetes kube-proxy, etc.)
- The goal: full migration, audit hybrid state, or troubleshoot an existing nftables setup
Your job:
1. **Detect the hybrid trap first**:
- Modern distros (Ubuntu 22.04+, RHEL 9+, Debian 11+) ship `iptables-nft` — the `iptables` command writes to nftables under the hood
- But `iptables-legacy` may also be installed for backward compat
- `nft list ruleset` shows the unified view (both old and translated rules)
- **Rules added with `iptables-legacy`** are invisible to `nft list ruleset` AND to `iptables-nft`
- **Symptom**: "I added a rule with `iptables` but it doesn't appear / doesn't work" → check `iptables-legacy -L` vs `iptables -L`
2. **Translate the ruleset**:
- `iptables-restore-translate` converts `iptables-save` output to nftables syntax
- `ip6tables-restore-translate` for IPv6
- The output is informative but may not be a 1:1 perfect replacement
- Review every line; some constructs need manual adjustment
3. **Key model differences** to surface:
- **Tables**: nftables tables are named arbitrarily (e.g., `inet filter`) and scoped to a family. iptables has fixed tables (`filter`, `nat`, `mangle`, `raw`).
- **`inet` family**: nftables has a unified `inet` family for IPv4+IPv6 — most cases simplify.
- **Chains**: nftables chains are user-defined; you assign them a type (`filter`, `nat`, `route`), hook (`input`, `output`, `forward`, `prerouting`, `postrouting`), and priority.
- **Default policy**: each base chain has its own policy (`accept` or `drop`) — not a global table policy.
- **Sets**: native first-class concept. Use for IP lists, port lists, dynamic blocklists. Replace `ipset`.
- **Maps and verdict maps**: route packets to chains based on key lookups. Very powerful.
- **Counters and quotas**: per-rule counters built in (no `-c` like iptables).
- **`accept` is final** unless `policy: drop` is on the chain. Order still matters.
4. **For each iptables construct**, translate:
- `-A INPUT -i lo -j ACCEPT` → `iif "lo" accept`
- `-A INPUT -p tcp --dport 22 -j ACCEPT` → `tcp dport 22 accept`
- `-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT` → `ct state established,related accept`
- `-A INPUT -m state --state INVALID -j DROP` → `ct state invalid drop`
- `-t nat -A POSTROUTING -o eth0 -j MASQUERADE` → in a `nat` chain with `postrouting` hook: `oifname "eth0" masquerade`
- `-m multiport --dports 80,443` → `tcp dport { 80, 443 }`
- `-m hashlimit ...` → `limit rate over 100/second`
5. **For coexistence with Docker / Kubernetes / fail2ban**:
- Docker uses iptables (via `iptables-nft` shim on modern distros) — its rules show up in `nft list ruleset`
- kube-proxy in iptables mode adds many rules; in ipvs mode adds different layer
- fail2ban writes to iptables-nft — verify it's targeting the right shim
- If you've replaced the iptables ruleset with custom nft, Docker may not be able to manage its chains correctly. Pick one model.
6. **Validate the new ruleset**:
- `nft -c -f <file>` — check syntax only (no apply)
- Apply atomically: `nft -f /etc/nftables.conf`
- **Always have a console / out-of-band path** before applying — a bad rule locks you out
- Test inbound from another host immediately after apply
7. **Persist**:
- `/etc/nftables.conf` is the canonical config location
- `systemctl enable nftables`
- On RHEL: `nft list ruleset > /etc/nftables.conf && systemctl restart nftables`
Mark DESTRUCTIVE: applying ruleset over SSH without console (lockout risk), flushing the ruleset (`nft flush ruleset`), removing chains Docker/kube-proxy depend on.
---
Distro + kernel: [DESCRIBE]
Current iptables backend: [iptables-nft / iptables-legacy / both]
`iptables-save`:
```
[PASTE]
```
Custom chains in use: [Docker / kube-proxy / fail2ban / other]
Goal: [full migration / audit / troubleshoot]
Why this prompt works
iptables migration looks straightforward — there’s a translator! — but the model differences (chain policy, table scoping, hybrid backends) cause silent breakage in production. This prompt forces an audit of both backends, then a translate-validate-test cycle.
How to use it
- Always check for hybrid first:
iptables-legacy -Landiptables -L(nft-backed) — they may not match. - Translate, then review.
iptables-restore-translateis a starting point, not a finish line. - Test in staging with the same daemons (Docker, fail2ban, kube-proxy).
- Apply via out-of-band access the first time.
Useful commands
# Detect backend in use
update-alternatives --display iptables # Debian/Ubuntu
alternatives --display iptables # RHEL/Fedora
iptables -V # shows "nf_tables" if shim
ls -la /usr/sbin/iptables # symlink target
# Both backends (check both!)
iptables-legacy -L -n -v --line-numbers
iptables -L -n -v --line-numbers # may be nft-backed
# nftables view (unified)
sudo nft list ruleset
sudo nft list tables
sudo nft list chains
sudo nft list set inet filter blocklist
# Translate
iptables-save > /tmp/iptables.rules
iptables-restore-translate -f /tmp/iptables.rules > /tmp/nft.rules
# Review /tmp/nft.rules carefully
# Validate without applying
sudo nft -c -f /tmp/nft.rules
# Apply
sudo nft -f /tmp/nft.rules
# Persist
sudo cp /tmp/nft.rules /etc/nftables.conf
sudo systemctl enable nftables
sudo systemctl restart nftables
# Switch from iptables-legacy to iptables-nft (Debian/Ubuntu)
sudo update-alternatives --set iptables /usr/sbin/iptables-nft
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-nft
# Stats / counters
sudo nft -a list ruleset # show handles
sudo nft list ruleset | grep -B1 counter
# Reset counters
sudo nft reset counters
Translation examples
Common iptables → nftables
# Allow established traffic
# iptables
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# nftables
ct state established,related accept
# Allow SSH from specific subnet
# iptables
-A INPUT -p tcp -s 10.0.0.0/24 --dport 22 -j ACCEPT
# nftables
ip saddr 10.0.0.0/24 tcp dport 22 accept
# Drop and log
# iptables
-A INPUT -j LOG --log-prefix "drop: "
-A INPUT -j DROP
# nftables
log prefix "drop: " drop
# Masquerade NAT
# iptables
-t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE
# nftables (in a nat chain hooked at postrouting)
oifname "eth0" ip saddr 10.0.0.0/24 masquerade
Modern nftables ruleset (full example)
# /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
set blocklist {
type ipv4_addr
flags interval, timeout
}
chain input {
type filter hook input priority filter; policy drop;
# Loopback
iif "lo" accept
# Established / related
ct state established,related accept
ct state invalid drop
# Blocklist
ip saddr @blocklist drop
# ICMP (with rate limit)
meta l4proto { icmp, ipv6-icmp } limit rate 10/second accept
# SSH from management subnet
ip saddr 10.0.0.0/24 tcp dport 22 accept
# HTTPS public
tcp dport { 80, 443 } accept
# Log + drop the rest
log prefix "nft-drop: " limit rate 5/minute
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
}
table inet nat {
chain postrouting {
type nat hook postrouting priority srcnat;
oifname "eth0" ip saddr 10.0.0.0/24 masquerade
}
}
Common findings this catches
- Rules added with
iptables“don’t work” — modern distro’siptablesisiptables-nft; rules ARE there, in nftables. Usenft list rulesetto see them. - Two parallel rulesets (legacy + nft) → confusion. Migrate to one shim and remove the other.
- fail2ban silent post-migration → fail2ban config references old chain/table; update to nft equivalents.
- Docker rules disappear after
nft flush ruleset→ restart docker daemon to re-create. policy accepton a chain that should be drop-by-default → effectively no firewall on that hook.- No conntrack rule → established traffic gets re-evaluated and possibly dropped on response. Always have
ct state established,related acceptnear the top. - kube-proxy in iptables mode + custom nft rules — heavy interaction; consider switching kube-proxy to ipvs mode.
When to escalate
- Production firewall change at scale — staged rollout with rollback plan; one node at a time.
- Docker / Kubernetes-managed rules disrupted by manual nft changes — coordinate with platform team; they may need to reconcile.
- Complex NAT scenarios (1:1 NAT, port forwarding, hairpin NAT) — verify each separately.
Related prompts
-
Linux Host Network Connectivity Debug Prompt
Diagnose single-host Linux networking — broken routes, firewall blocks, DNS, conntrack exhaustion, ephemeral port exhaustion, MTU issues — without confusing it with cloud/SDN problems.
-
Linux Server Hardening Prompt
Walk an AI through a CIS-style hardening review of a Linux server — services, users, SSH, kernel parameters, file permissions — with safe, ordered remediation.
-
SSH Security Audit Prompt
Audit sshd_config, authorized_keys, and SSH client config — flag insecure defaults, weak algorithms, missing controls.