Add and Safely Verify an iptables Rule

One wrong iptables rule on a remote box and your SSH session goes quiet for good. Here is how to add a single harmless rule, prove it is there, and take it out again. You will inspect the IPv4 filter table, add one narrowly scoped rule, check its position and counters, then remove it. The same workflow applies to ip6tables for IPv6. Allow about fifteen minutes, plus time to pin down the correct interface, address and service before you touch a live firewall.

Warning: Do not run the state-changing examples on a remote machine until you have a tested recovery path.

Before you start

$ iptables --version
iptables v1.8.10 (nf_tables)
$ ip6tables --version
ip6tables v1.8.10 (nf_tables)

List the current IPv4 filter rules numerically, with counters and rule numbers. This is read-only:

$ sudo iptables -L INPUT -n -v --line-numbers
Chain INPUT (policy ACCEPT)
num   pkts bytes target     prot opt in  out source      destination
...

The exact rows depend on the machine.

Tip: Always name the table when inspecting NAT rules: sudo iptables -t nat -L -n -v --line-numbers. A classic mistake is staring at filter while the rule you want lives in nat.

1. Add one safe test rule

For a harmless test, append a logging rule for traffic from an address you control. It does not accept or reject anything; it just bumps its counters and asks the kernel to log matching packets.

$ sudo iptables -A INPUT -p tcp -s 198.51.100.24 --dport 443 -j LOG --log-prefix "iptables-test "

Warning: Do not use -j ACCEPT or -j DROP as a casual test. Those targets change packet handling immediately.

For IPv6, use the sibling command and an IPv6 source address:

$ sudo ip6tables -A INPUT -p tcp -s 2001:db8:1234::24 --dport 443 -j LOG --log-prefix "ip6tables-test "

Both example addresses are reserved for documentation, so substitute a real address before expecting a match. The rule is live as soon as the command succeeds, and it is normally runtime state, not a permanent configuration file.

2. Verify the rule and its counters

List the chain again. The new rule should sit at the end, with a number, a target and zero or rising counters:

$ sudo iptables -L INPUT -n -v --line-numbers
Chain INPUT (policy ACCEPT)
num   pkts bytes target     prot opt in  out source        destination
...   0    0     LOG        tcp  --  *   *   198.51.100.24  0.0.0.0/0  tcp dpt:443 LOG flags 0 level 4 prefix "iptables-test "

Formatting varies with the installed extensions, so check what the rule means rather than matching the display character for character. For review and backup, -S gives the more exact form:

$ sudo iptables -S INPUT
-P INPUT ACCEPT
...
-A INPUT -s 198.51.100.24/32 -p tcp -m tcp --dport 443 -j LOG --log-prefix "iptables-test "

To test for the rule in a script without changing anything, use -C with the same specification. The exit status tells you whether a matching rule exists:

$ sudo iptables -C INPUT -p tcp -s 198.51.100.24 --dport 443 -j LOG --log-prefix "iptables-test "
$ printf 'status=%s\n' "$?"
status=0

Checkpoint: status=0 means the rule is there. A non-zero result means the specification did not match an existing rule; it is not proof that the firewall is down.

Tip: Do not put another command between iptables -C and the status check, or $? will belong to that command instead.

3. Remove the test rule

Removal changes state. Delete by the full specification, because line numbers shift whenever rules are inserted or removed:

$ sudo iptables -D INPUT -p tcp -s 198.51.100.24 --dport 443 -j LOG --log-prefix "iptables-test "
$ sudo iptables -C INPUT -p tcp -s 198.51.100.24 --dport 443 -j LOG --log-prefix "iptables-test "
$ printf 'status=%s\n' "$?"
status=1

Checkpoint: status=1 after the delete means the rule has gone.

Warning: Never use -F as a shortcut. Flushing a chain deletes all its rules, and leaving out the chain flushes every chain in the selected table.

4. Save state only after review

The iptables command changes the kernel's current ruleset. Persistence is a separate job, handled by the distribution or firewall manager. Before saving anything, review the full ruleset and work out which service owns it:

$ sudo iptables-save
$ sudo ip6tables-save

For a reversible snapshot during a maintenance window, save to an explicitly named root-owned location and inspect the files before you ever restore them:

$ sudo sh -c 'iptables-save > /root/iptables-before-change.rules'
$ sudo sh -c 'ip6tables-save > /root/ip6tables-before-change.rules'

Warning: Restoring is disruptive and can replace the whole active ruleset. Only restore a known-good file, and only with an out-of-band recovery route.

$ sudo iptables-restore < /root/iptables-before-change.rules
$ sudo ip6tables-restore < /root/ip6tables-before-change.rules

Common traps

Done means