A dropped SSH session after a firewall change is the classic xtables-legacy horror story, and this walks through avoiding it. You will identify whether this host is using the old xtables kernel API, save the active IPv4 and IPv6 rules, inspect them, and make one guarded rule change with a matching undo command ready to go.
Checkpoint: stop after step 3 if you only needed a backup and a look around. Steps 5 and 6 change live firewall state and need elevated privileges.
xtables-legacy is the legacy family of iptables commands. It talks to the kernel through the older getsockopt and setsockopt interface, not the nf_tables API the xtables-nft variants use. On this host, the installed iptables package is 1.8.10-3ubuntu2 and the command reports the legacy backend directly:
$ dpkg-query -W -f='${Package} ${Version}\n' iptables
iptables 1.8.10-3ubuntu2
$ iptables-legacy -V
iptables v1.8.10 (legacy)
That version suffix is the part that matters. Do not assume a bare iptables command selects this backend: distributions can point that name at legacy or nf_tables alternatives depending on how they are configured. Use the explicit -legacy names in a script whenever the backend actually matters.
The legacy commands are all one multi-call binary underneath. The separate names just select its behaviour: IPv4, IPv6, save and restore modes:
$ command -v iptables-legacy ip6tables-legacy iptables-legacy-save iptables-legacy-restore
/usr/sbin/iptables-legacy
/usr/sbin/ip6tables-legacy
/usr/sbin/iptables-legacy-save
/usr/sbin/iptables-legacy-restore
$ readlink -f "$(command -v iptables-legacy)"
/usr/sbin/xtables-legacy-multi
Use iptables-legacy for IPv4 and ip6tables-legacy for IPv6. Swapping them is not a harmless spelling slip: an IPv4 address or rule can simply be invalid for the IPv6 command.
Saving looks read-only but reads protected kernel state, so run it with sudo when your account cannot reach the tables directly. Keep the files somewhere private, because firewall rules can expose internal addresses and service details:
$ sudo iptables-legacy-save > ipv4-before.rules
$ sudo ip6tables-legacy-save > ipv6-before.rules
$ wc -l ipv4-before.rules ipv6-before.rules
12 ipv4-before.rules
10 ipv6-before.rules
22 total
Your line counts will differ. An empty file is not proof the firewall is empty either; check the command's exit status and error output first. A successful save is a text snapshot you can compare later and feed straight into the matching restore command.
Checkpoint: keep both files until the new rules have been tested. On a remote machine, copy them somewhere you can still reach without relying on the very firewall change you are about to make.
Listing rules needs the xtables lock and kernel tables, so use sudo. -n skips reverse DNS lookups, and --line-numbers makes a later rule deletion unambiguous:
$ sudo iptables-legacy -w 5 -L --line-numbers -n
$ sudo ip6tables-legacy -w 5 -L --line-numbers -n
$ sudo iptables-legacy -S
$ sudo ip6tables-legacy -S
-L shows chains and counters; -S prints rules in command-like form. The default table is filter, so a rule sitting in another table simply will not show up here. Check a specific table with -t TABLE, for example:
$ sudo iptables-legacy -t nat -L --line-numbers -n
-w 5 waits up to five seconds for the xtables lock. It helps coordinate separate invocations, but it does not turn a series of commands into one transaction.
Do not start by appending a rule straight to a remote host. Replace the placeholder address and port with the service you actually intend to expose, then check whether an identical rule already exists. This step does not touch the ruleset:
$ sudo iptables-legacy -w 5 -C INPUT -p tcp -s 192.0.2.44 --dport 22 -j ACCEPT
$ printf 'check status: %s\n' "$?"
check status: 1
Status 0 means the rule already exists. A non-zero status means it was not found, or the check itself failed, so read the diagnostic before proceeding. 192.0.2.44 is documentation space; it is not a useful source address for a real deployment.
Warning: an ACCEPT rule can expose a service, and a DROP or REJECT rule can cut you off. Confirm the current management path, source address and service before changing a policy. Keep an existing session open while testing a remote firewall.
If the rule is intended and the check came back non-zero for the right reason, append it. This changes live IPv4 firewall state and needs root:
$ sudo iptables-legacy -w 5 -A INPUT -p tcp -s 192.0.2.44 --dport 22 -j ACCEPT
$ sudo iptables-legacy -L INPUT --line-numbers -n
Note the line number for the new rule, but use the full rule specification for the undo, so a later insertion elsewhere does not leave that line number stale:
$ sudo iptables-legacy -w 5 -D INPUT -p tcp -s 192.0.2.44 --dport 22 -j ACCEPT
$ sudo iptables-legacy -C INPUT -p tcp -s 192.0.2.44 --dport 22 -j ACCEPT
$ printf 'after-delete status: %s\n' "$?"
after-delete status: 1
That deletion removes the one matching rule, not every rule with similar intent. If the append or delete misbehaves, do not improvise with -F: flushing a chain can take out unrelated protections along with it. Go back to the saved snapshot and investigate the actual error instead.
Restoring can replace the live ruleset outright and cut off your session. Check the file first, use the matching address family, and arrange console or out-of-band access before you run it:
$ sed -n '1,12p' ipv4-before.rules
$ sudo iptables-legacy-restore < ipv4-before.rules
$ sudo iptables-legacy -L --line-numbers -n
Use ip6tables-legacy-restore for the IPv6 snapshot. Treat a file from another host as untrusted configuration: review every table, chain policy and rule before applying it. The restore command also has a syntax-check mode, but a clean syntax check is not a substitute for a maintenance window and a tested recovery path.
The local manual flags an important limitation: an append or insert reads the active ruleset, changes a copy, then commits it. Two concurrent writers can lose one another's updates that way. Use -w for lock coordination, keep changes short, and avoid firing several independent mutations from different jobs at once. The lock is not a full transaction across a shell script.
There is no legacy equivalent of a live ruleset change monitor. Run iptables-legacy-save periodically and diff its output against the approved snapshot instead. The manual is also explicit that xtables-monitor needs the xtables-nft variants, so do not expect it to report anything made through these legacy commands.
(legacy) in the version output.xtables-monitor to see legacy changes.