Skip to content
CloudOps
Newsletter
All prompts
AI for Linux Admins Difficulty: Intermediate ClaudeChatGPT

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

  1. Always check for hybrid first: iptables-legacy -L and iptables -L (nft-backed) — they may not match.
  2. Translate, then review. iptables-restore-translate is a starting point, not a finish line.
  3. Test in staging with the same daemons (Docker, fail2ban, kube-proxy).
  4. 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’s iptables is iptables-nft; rules ARE there, in nftables. Use nft list ruleset to 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 accept on 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 accept near 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

Newsletter

Free: the DevOps AI Incident-Triage Cheat Sheet

Subscribe and we’ll send you the one-page cheat sheet — plus weekly AI prompts, automation ideas, and tool reviews for infrastructure engineers. One email a week. No spam, unsubscribe anytime.

  • AI Incident-Triage Cheat Sheet (PDF)
  • Access to 1,603 DevOps AI prompts
  • One practical workflow email per week