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.
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.
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.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.
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.LOG is an extension target, so its exact options come from the installed iptables-extensions(8) manual.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.
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.
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.
--line-numbers first, use the displayed number straight away (sudo iptables -D INPUT 7), then list again.ip6tables and the exact IPv6 specification.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.
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
/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.-C. You tested the rule and understood its exit status.ACCEPT/DROP policy without a tested recovery route.