Home / Alt manpages / iptables(8)

  • iptables(8)
  • Admin command
  • linux

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.

  • Version. iptables 1.8.10-3ubuntu2, backed by the nf_tables kernel API on this machine.
  • Privileges. A root shell or sudo, because these commands read and change kernel firewall state.

Warning

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

Before you start

  • Pick the address family. iptables manages IPv4 rules and ip6tables manages IPv6 rules. Same command shape, but an IPv4 address is not valid in an IPv6 rule and vice versa.
  • Know the table. The default table is filter. Its built-in chains are INPUT (packets for local sockets), FORWARD (routed packets) and OUTPUT (locally generated packets).
$ 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.

  • -n skips reverse DNS and service-name lookups.
  • -v shows interfaces and counters.
  • --line-numbers makes deletion by position possible later.

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 "
  • -A appends to the end of the selected chain.
  • The match is TCP traffic from the source address to destination port 443.
  • LOG is an extension target, so its exact options come from the installed iptables-extensions(8) manual.
  • Use a quiet source. Logging can produce a lot of messages, so pick a source that will not generate sustained traffic.

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.

  • Deleting by number? List the chain with --line-numbers first, use the displayed number straight away (sudo iptables -D INPUT 7), then list again.
  • Undoing the IPv6 test? Repeat the deletion with ip6tables and the exact IPv6 specification.
  • Got an error? If a command reports an unknown chain, invalid match or permission error, stop and check the spelling and address family. Do not broaden the rule to make the error go away.

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

  • Another manager owns the firewall. Do not casually mix this workflow with nftables, firewalld, Docker or another manager. A later reload may wipe your manual rule, and a manual flush may remove rules a service needs.
  • The lock is held. The installed manpage says iptables uses /run/xtables.lock for exclusive access. In a controlled script, use -w or -w 5 to wait for another iptables process instead of failing immediately.

Done means

  • Right family. You know whether the rule is IPv4 or IPv6 and used the matching command.
  • Right table and chain. You inspected them with numeric output and line numbers.
  • Checked with -C. You tested the rule and understood its exit status.
  • Cleaned up. You removed the temporary rule by its full specification, or recorded the exact recovery file.
  • Nothing flushed. You did not flush a live table or set an ACCEPT/DROP policy without a tested recovery route.