← Start of tutorial

WIUNIX ACADEMY

Atomic nftables changes: rehearse timeout rollback before risking remote access

Page 3 of 4. Review the earlier steps and prerequisites before continuing.

YOUR SYSTEM. YOUR STEPS.

Where are you working?

Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.

Rocky Linux 9Reviewed against official documentation ·

USE YOUR OWN VALUES

Configure this tutorial

Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.

Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.

Set values for my environment8 editable values
09

LET’S BUILD

Arm rollback before applying the deliberately broken policy

Run this whole panel together. A root shell takes an exclusive lock, rechecks the candidate, creates the detached timeout timer and verifies it is active before loading the candidate. The timeout service takes the same lock, so if its deadline arrives during a slow apply, it waits and restores after that transaction releases the lock. This avoids restoring first and then accidentally applying a late candidate.

The lock coordinates only this fixture’s two commands. It does not lock out firewalld, another operator or a process that ignores the lock. Do not modify the snapshot, namespace names or files while the timer is armed. Loss of the terminal does not normally cancel the system timer, but reboot, manager failure, namespace deletion or an unsuccessful restore still defeats recovery.

The immediate ping is expected to receive no echo reply. Exit 1 alone can reflect loss on another boundary, so confirm the intended drop counter increased before attributing the loss to this rule. A different ping error stops the panel. Leave rollback armed. If the ping succeeds, the timer may already have restored the baseline or the candidate did not affect the tested path; inspect the actual result.

bash

Terminal on the disposable lab VM

ip_bin=$(sudo sh -c 'command -v ip') || exit 1
nft_bin=$(sudo sh -c 'command -v nft') || exit 1
flock_bin=$(sudo sh -c 'command -v flock') || exit 1
sudo /bin/sh -c 'set -eu
exec 9>"$1/change.lock"
"$4" -x 9
test "$(systemctl show -p LoadState --value "$2.timer")" = not-found
test "$(systemctl show -p LoadState --value "$2.service")" = not-found
"$5" netns exec "$6" "$7" -c -f "$1/candidate.nft"
systemd-run --unit="$2" --on-active="$3"s --timer-property=AccuracySec=1s --property=Type=oneshot --property=RemainAfterExit=yes "$4" -x "$1/change.lock" "$5" netns exec "$6" "$7" -f "$1/rollback.nft"
systemctl is-active --quiet "$2.timer"
"$5" netns exec "$6" "$7" -f "$1/candidate.nft"
' sh '/var/lib/academy-nft-rollback-lab' 'academy-nft-rollback' '90' "$flock_bin" "$ip_bin" 'academy-nft-server' "$nft_bin" || exit 1
if sudo ip netns exec 'academy-nft-client' ping -c 1 -W 2 '192.0.2.1'; then
  printf 'UNEXPECTED: candidate did not block the lab probe\n' >&2
  exit 1
else
  probe_status=$?
  if test "$probe_status" -ne 1; then
    printf 'Probe error, not evidence of a firewall drop: exit=%s\n' "$probe_status" >&2
    exit "$probe_status"
  fi
  printf 'No echo reply; inspect the intended drop counter; leave rollback armed\n'
fi
sudo ip netns exec 'academy-nft-server' nft -n list table inet '"academy_guard"' || exit 1
systemctl list-timers --all 'academy-nft-rollback.timer'
How do I know it worked?

The timer was armed before a single atomic table change, and the fresh lab probe is blocked while the deliberate drop rule is active.

10

LET’S BUILD

Wait for rollback and verify the path, not just the timer

Wait through the configured interval, then inspect the restore service, table and fresh client probe. The relative timer is not real-time scheduling; allow for manager load and the lock if a result is not yet available. A timer firing is not proof its command succeeded.

Accept recovery only when the service result is successful, the baseline allow is back, and the new probe succeeds. This is a lab ICMP recovery result, not proof that an existing production SSH session or a new SSH login would survive a real host-policy change.

bash

Terminal on the disposable lab VM

sleep '90'
sudo systemctl show 'academy-nft-rollback.service' -p ActiveState -p Result -p ExecMainStatus
sudo journalctl -u 'academy-nft-rollback.service' --no-pager -n 40
test "$(systemctl show -p ActiveState --value 'academy-nft-rollback.service')" = active || exit 1
test "$(systemctl show -p SubState --value 'academy-nft-rollback.service')" = exited || exit 1
test "$(systemctl show -p Result --value 'academy-nft-rollback.service')" = success || exit 1
test "$(systemctl show -p ExecMainStatus --value 'academy-nft-rollback.service')" = 0 || exit 1
sudo ip netns exec 'academy-nft-server' nft -n list table inet '"academy_guard"'
sudo ip netns exec 'academy-nft-client' ping -c 2 -W 2 '192.0.2.1' || exit 1
How do I know it worked?

The restore exits zero, the baseline allow rule is present and a new isolated probe succeeds.

11

LET’S BUILD

If automatic recovery fails, restore only this fixture from the host console

Use the VM host console to inspect the failure first. The host is outside the filtered namespace. The following restore takes the same lock and reloads only the captured table, then checks the probe. It preserves the files and timer evidence for diagnosis.

If the namespace or table was deleted or another object now owns the name, stop rather than making the snapshot fit an unknown state. A restore that cannot delete its expected table fails; it is not a reason to flush the host or disable the firewall.

bash

Terminal on the disposable lab VM

ip_bin=$(sudo sh -c 'command -v ip') || exit 1
nft_bin=$(sudo sh -c 'command -v nft') || exit 1
sudo flock -x '/var/lib/academy-nft-rollback-lab/change.lock' "$ip_bin" netns exec 'academy-nft-server' "$nft_bin" -f '/var/lib/academy-nft-rollback-lab/rollback.nft' || exit 1
sudo ip netns exec 'academy-nft-client' ping -c 2 -W 2 '192.0.2.1' || exit 1
How do I know it worked?

The table-scoped manual restore succeeds and connectivity returns; otherwise the precise restore error remains an open failure.

Follow the specific failed boundary

Each diagnostic below reads the lab or its named unit. Retain errors and resolve missing permission with the VM owner. No command disables SELinux/AppArmor, changes a host firewall manager or broadens host packet policy.

Namespace creation is denied

Check the actual VM, kernel and named namespace inventory. Container restrictions or missing namespace/capability support can block creation. Do not rerun the experiment in the host namespace as a substitute.

bash
uname -r
cat /proc/1/comm
sudo ip netns list
sudo journalctl -k --since '-10 min' --no-pager -n 60
nft validation fails

Inspect the actual tool version and exact namespace table. A syntax/feature error or a missing expected table must be fixed before applying. Checks require kernel access; they are not merely an offline text parser.

bash
sudo nft --version
sudo ip netns exec 'academy-nft-server' nft -n list table inet '"academy_guard"'
sudo ip netns exec 'academy-nft-server' nft -c -f '/var/lib/academy-nft-rollback-lab/candidate.nft'
The timer fired but connectivity stayed broken

Read the command result, journal and current table. A missing namespace, inaccessible file or restore error is different from delayed scheduling. Confirm the lock is not held by another fixture operation before using the console restore.

bash
systemctl show 'academy-nft-rollback.timer' 'academy-nft-rollback.service' -p Id -p ActiveState -p SubState -p Result -p ExecMainStatus
sudo journalctl -u 'academy-nft-rollback.service' --no-pager -n 60
sudo ip netns exec 'academy-nft-server' nft -n list table inet '"academy_guard"'
The initial or restored probe fails without a drop-counter increase

Verify link state, addresses and the directly connected route in both namespaces. A failed probe can be an addressing error rather than a filter hit; do not add a host route or enable forwarding to hide the wrong fixture topology.

bash
sudo ip -n 'academy-nft-server' -br address
sudo ip -n 'academy-nft-client' -br address
sudo ip -n 'academy-nft-client' route get '192.0.2.1'
sudo ip netns exec 'academy-nft-server' nft -n list table inet '"academy_guard"'

Translate the lesson into a real change plan

Before any separately authorized host change, identify the single owning manager and exact policy boundary, capture its versioned before-state, and arrange working out-of-band console access. A second existing SSH shell is useful but not enough: established-connection rules can keep it alive while all new logins fail. Test a new independent management connection plus required application paths, IPv6, DNS and forwarded/container traffic from their real origins.

A default-drop host needs a reviewed management-source allow and established/related behavior before activation. The lab’s ICMP policy is not that policy. An accept in your table may still meet a drop in another chain; never treat a private table name as automatic coexistence. Let firewalld/UFW or the application’s supported integration own their policy, and do not edit Docker-, Fail2ban- or CrowdSec-owned tables behind their controllers.

A real success path cancels its pending rollback only after all acceptance checks and concurrent-change guards pass. If a rollback is already running, reconcile its actual result before declaring the candidate committed. Cancellation, current runtime rules and boot persistence are separate states. This article deliberately lets the failed lab change roll back rather than supplying a generic production commit command.

12

LET’S BUILD

Inspect persistence separately; do not install the lab rules at boot

Runtime nft changes vanish when their namespace is destroyed. This experiment writes no boot policy. Inspect the installed service file before adapting any real persistent policy: Ubuntu/Debian and Arch normally use their nftables main configuration file, while RHEL-family service loading uses its sysconfig include file. Environment notes identify the native paths; the actual unit is the final authority.

Reload/start/stop behavior can run broad commands. For example, the reviewed Debian nftables unit flushes the entire host ruleset when stopped. Do not stop or restart that service to clean up a namespace table. With firewalld, runtime and permanent state are separate; with NetworkManager, connection zone settings may be reapplied. Persist a separately approved change only through its owning manager, then qualify its reload/reboot recovery in a disposable clone first.

bash

Terminal on the disposable lab VM

systemctl cat nftables.service
systemctl show nftables.service -p FragmentPath -p DropInPaths -p ExecStart -p ExecReload -p ExecStop
systemctl show firewalld.service ufw.service NetworkManager.service -p Id -p LoadState -p ActiveState
How do I know it worked?

The actual host loader, files and lifecycle behavior are known; no lab rule is persisted and no service is changed.

Explore all tutorials ↗