Build a baseline nftables firewall
Create a default-deny host firewall with loopback, established traffic, controlled SSH, service ports, logging, validation, and rollback.
Limit inbound traffic to explicitly approved services while preserving administration access.
- Ubuntu Server 22.04 LTS, 24.04 LTS
- nftables 1.x
- Console access A malformed policy can immediately block SSH.
- Service inventory Map every listener to an owner and approved source.
sudo ss -lntup - Ruleset backup Export active and persistent rules before changing them.
sudo nft list ruleset
OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.
Full guide
What you will build
- A persistent nftables ruleset with a default-drop input policy, explicit administration and web-service allows, stateful return traffic, essential ICMP, and rate-limited drop logging.
- A repeatable safety workflow that inventories listeners, captures the active policy, validates a candidate without applying it, and schedules an automatic rollback before the live change.
- A verified boot-time nftables service whose on-disk and active rulesets match after a second-session reachability test.
- Only the approved management source can reach SSH, public clients can reach TCP 80 and 443, and unsolicited inbound traffic is dropped.
- Existing established connections and essential IPv4/IPv6 control traffic continue to work.
- A known-good ruleset is available under /root and a temporary systemd timer can restore it if the administrator loses connectivity.
- nft --check succeeds, a new external SSH connection works, published ports behave as planned, and nftables is enabled for reboot.
Architecture
How the parts fit together
Packets traverse an inet-family input chain before reaching local services. Connection tracking accepts return traffic, explicit rules admit approved new traffic, and the terminal drop policy rejects everything else. A separate forward chain remains closed because this baseline is for a host, not a router.
- A packet addressed to the host reaches the input hook.
- Loopback and established or related traffic are accepted first; invalid state is dropped.
- Essential ICMP, trusted SSH and approved service ports are matched explicitly.
- Unmatched packets may produce a rate-limited log entry and then meet the chain's drop policy.
Assumptions
- The target is an Ubuntu server that uses nftables as its authoritative host firewall. It is not simultaneously managed by UFW, firewalld, Docker-generated rules, Kubernetes or another controller.
- Console or provider recovery access is tested, the current SSH session remains open, and a second client is available for reachability checks.
- The administrator knows the real SSH port, management source CIDR, public services, IPv4 and IPv6 requirements, and every listening socket owner.
- This is a host baseline with forward policy drop. A VPN gateway, container router or hypervisor needs a separately designed forward and NAT policy.
- The example permits public HTTP and HTTPS. Remove those rules when the host does not actually serve them.
Key concepts
- Ruleset
- The complete nftables object graph currently loaded in the kernel, including tables, chains, sets, maps and rules.
- Base chain
- A chain attached to a Netfilter hook with a type, priority and policy. The input base chain sees traffic addressed to the local host.
- Default deny
- A policy where unmatched traffic is dropped and every accepted path must be declared explicitly.
- Connection tracking
- Kernel state that classifies packets as new, established, related or invalid, allowing reply traffic without broad reverse rules.
- inet family
- A table family that can match both IPv4 and IPv6 while still permitting protocol-specific expressions.
- Atomic load
- nft parses a complete batch and commits it as one transaction, avoiding a partially loaded policy when a statement fails.
Before you copy
Values used in this guide
{{host}}Public DNS name or address of the server under test.
Example: server.example.com{{managementCidr}}Trusted source network allowed to initiate SSH.
Example: 203.0.113.0/24{{sshPort}}Actual TCP port used by the SSH listener.
Example: 22Security and production boundaries
- A default-drop policy is only as accurate as the inventory. Missing DNS, monitoring, cluster, backup or management paths can create an outage even when syntax is valid.
- IPv6 is not disabled by using an inet table. Test it explicitly; an IPv4-only validation can miss an exposed or broken IPv6 path.
- Logging every dropped packet can be a denial-of-service vector. The rate limit protects log capacity but must be tuned with real traffic and monitoring.
- Do not mix independent firewall managers. Their competing reloads can reorder, flush or recreate rules unexpectedly.
Stop before continuing if
- Stop if you cannot identify every listener or decide whether it should be reachable.
- Stop if another firewall manager owns the host, Docker or Kubernetes depends on generated rules, or the server forwards traffic.
- Stop if the known-good backup cannot be read by root or the rollback timer cannot be scheduled.
- Stop on any nft --check error; never test syntax by loading it live.
- Stop if a new SSH session or any required health check fails, and allow or trigger rollback before closing the original session.
command
Inventory listening services
Decide whether each socket needs external access.
Why this step matters
Firewall policy must follow the actual service inventory; otherwise a default drop can cause an outage or an obsolete listener can remain accidentally exposed.
What to understand
ss shows TCP and UDP listening sockets, numeric ports, owning processes and every bound address. A wildcard address such as 0.0.0.0 or [::] is reachable on more interfaces than loopback.
Record an owner, purpose, expected source and monitoring check for each socket before translating it into an allow rule.
System changes
- No persistent changes; reads kernel socket and process metadata.
Syntax explained
-l- Shows listening sockets.
-n- Keeps addresses and ports numeric instead of resolving names.
-t / -u- Includes TCP and UDP sockets.
-p- Shows the process using a socket when permissions allow.
sudo ss -lntuptcp LISTEN 0 4096 0.0.0.0:22 users:(("sshd",pid=812,fd=3))Checkpoint: Approve every listener
sudo ss -lntupContinue whenEvery wildcard listener has a documented owner and intended source network.
Stop whenAn unknown listener exists or the real management port is not known.
Security notes
- Do not publish a port merely because it is listening; bind private services to loopback where possible.
Alternatives
- Use systemd socket inventory and application configuration as supporting evidence.
Stop conditions
- Do not write the allowlist from assumptions or an old diagram.
command
Back up the current rules
Save the active ruleset and persistent file with a timestamp.
Why this step matters
Capture both the active kernel policy and the persistent file because either may be the only known-good recovery source after a failed remote change.
What to understand
The root shell performs redirection into /root, avoiding the common mistake where sudo applies only to nft while the unprivileged shell cannot create the backup file.
cp --archive preserves ownership, mode and timestamps of the on-disk configuration. Verify both artifacts before scheduling rollback.
System changes
- Creates /root/nftables-before.nft and /etc/nftables.conf.bak as recovery artifacts.
Syntax explained
sudo sh -c- Runs the redirection and nft command together with root privileges.
nft list ruleset- Serializes the active kernel ruleset in nft syntax.
cp --archive- Copies the persistent file while preserving metadata.
sudo sh -c 'nft list ruleset > /root/nftables-before.nft' && sudo cp --archive /etc/nftables.conf /etc/nftables.conf.bakRuleset and configuration backups created
Checkpoint: Validate the recovery artifacts
sudo test -s /root/nftables-before.nft && sudo nft --check --file /root/nftables-before.nft && sudo test -s /etc/nftables.conf.bakContinue whenBoth files are non-empty and the captured active ruleset parses successfully.
Stop whenAny backup is missing, empty or invalid.
If this step fails
The shell reports Permission denied for /root/nftables-before.nft.
Likely causeRedirection was performed by the unprivileged shell outside sudo.
idsudo test -w /root
ResolutionUse sudo sh -c so the process opening the destination file runs as root.
Security notes
- Rules may contain internal addresses and security policy; keep the backup root-readable.
Alternatives
- Store a versioned encrypted copy in approved configuration management in addition to the local emergency file.
Stop conditions
- Do not proceed without a parseable known-good ruleset.
config
Write a default-deny input policy
Accept loopback, established traffic, essential ICMP, trusted SSH, and approved service ports.
Why this step matters
A complete candidate makes the intended trust boundaries reviewable before the kernel changes and avoids an incremental sequence that temporarily leaves the host open or unreachable.
What to understand
The inet family combines IPv4 and IPv6 filtering. The input chain's drop policy is reached only after explicit loopback, state, ICMP, management and public-service accepts.
The forward chain remains closed and output remains allowed. Rate-limited logging is diagnostic evidence, not an accept or drop verdict by itself; the chain policy performs the final drop.
System changes
- Replaces /etc/nftables.conf as the persistent host policy when the operator saves the reviewed template.
Syntax explained
flush ruleset- Removes existing nftables objects when this complete file is loaded.
table inet filter- Creates one dual-stack filtering table.
hook input priority filter; policy drop- Attaches the chain to locally destined packets with default denial.
ct state established,related accept- Keeps replies and related flows working.
ct state invalid drop- Drops packets connection tracking cannot associate with a valid flow.
ip protocol icmp / ip6 nexthdr ipv6-icmp- Allows essential IPv4 and IPv6 control messages.
ip saddr ... tcp dport ... accept- Limits new SSH connections to the trusted source CIDR and selected port.
limit rate 5/second log- Logs unmatched packets at a bounded rate before the default drop.
/etc/nftables.confValues stay on this page and are never sent or saved.
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
iifname "lo" accept
ct state established,related accept
ct state invalid drop
ip protocol icmp accept
ip6 nexthdr ipv6-icmp accept
ip saddr {{managementCidr}} tcp dport {{sshPort}} accept
tcp dport { 80, 443 } accept
limit rate 5/second log prefix "nft-input-drop: "
}
chain forward { type filter hook forward priority filter; policy drop; }
chain output { type filter hook output priority filter; policy accept; }
}Persistent ruleset saved
Checkpoint: Perform a line-by-line policy review
Continue whenVariables are replaced, management source and port are exact, required public ports are present, and no competing manager depends on the flushed tables.
Stop whenA variable is unresolved, IPv6 intent is unknown, or the host forwards traffic.
Security notes
- flush ruleset is destructive to tables created by Docker, Kubernetes or other managers.
Alternatives
- Manage an isolated table without flushing the global ruleset when another system owns other tables.
Stop conditions
- Never copy this complete baseline unchanged onto a multi-manager or routing host.
verification
Validate without applying
A parse error must stop the change.
Why this step matters
Dry parsing is the mandatory safety gate between editing and applying; a malformed batch must not touch the live policy.
What to understand
nft --check evaluates syntax and object references without committing the transaction.
Success is intentionally silent. Treat exit code 0, not a particular printed sentence, as the evidence.
System changes
- No persistent or live firewall changes.
Syntax explained
--check- Validates commands without applying them.
--file- Reads the complete candidate from the named file.
sudo nft --check --file /etc/nftables.confNo output; exit code 0
Checkpoint: Require a clean exit
sudo nft --check --file /etc/nftables.conf; echo "exit=$?"Continue whenNo parser error and exit=0.
Stop whenAny diagnostic is printed or the exit code is non-zero.
Security notes
- A valid file can still implement the wrong policy; continue with rollback and external tests.
Alternatives
- Validate a generated file in CI as an additional gate, not a replacement for host-version validation.
Stop conditions
- Never use nft -f as a syntax test on a remote production host.
warning
Schedule a temporary rollback
Before applying remotely, arrange restoration from a separate session or timed unit.
Why this step matters
A timed rollback limits a remote lockout even when the candidate is syntactically valid but the management allow rule is wrong.
What to understand
systemd-run creates a transient timer and service. After five minutes, the service loads /root/nftables-before.nft unless the timer is explicitly stopped after successful testing.
The rollback file must already exist and parse. Keep console access because a timer is an additional safety layer, not a guarantee against systemd or filesystem failure.
System changes
- Creates transient nft-rollback.timer and nft-rollback.service units scheduled for five minutes.
Syntax explained
--unit=nft-rollback- Names the transient service and timer for clear inspection and cancellation.
--on-active=5m- Schedules execution five minutes after creation.
/usr/sbin/nft -f- Loads the captured known-good ruleset when the timer fires.
sudo systemd-run --unit=nft-rollback --on-active=5m /usr/sbin/nft -f /root/nftables-before.nftRunning timer as unit: nft-rollback.timer
Checkpoint: Verify rollback is armed
systemctl list-timers nft-rollback.timer --no-pagerContinue whenThe timer is listed with a future activation time.
Stop whenThe timer is absent, failed, or references an unreadable backup.
Security notes
- Do not cancel the timer from the original session before a new external session proves access.
Alternatives
- Use a provider console or out-of-band automation that can restore the policy independently.
Stop conditions
- Do not apply from a remote-only session unless both timed and console recovery are available.
command
Apply atomically
Load the validated file and keep the original connection open.
Why this step matters
Load the already validated complete batch in one transaction, then inspect the kernel's effective rules while the old SSH session and rollback timer remain available.
What to understand
nft --file commits the complete candidate atomically or reports a failure. The chained list reads back what the kernel accepted.
Do not edit again between check and apply. If the file changes, repeat validation and confirm the rollback timer still points to the known-good capture.
System changes
- Replaces the active nftables ruleset and may immediately alter all host network reachability.
Syntax explained
nft --file- Loads the full candidate as an nft transaction.
&&- Lists the active policy only after a successful load.
nft list ruleset- Reads back the complete effective kernel ruleset.
sudo nft --file /etc/nftables.conf && sudo nft list rulesettable inet filter { ... policy drop; ... }Checkpoint: Inspect the effective input chain
sudo nft -a list chain inet filter inputContinue whenPolicy drop and the reviewed loopback, state, ICMP, management and service accepts are present.
Stop whenAny required rule is missing or the active chain differs from the validated file.
If this step fails
The apply command succeeds but a required service becomes unreachable.
Likely causeThe valid ruleset omitted the real address family, port, interface or source.
Keep the original session openInspect counters and rate-limited logsTrigger sudo nft -f /root/nftables-before.nft if recovery is needed
ResolutionRestore first, then correct the inventory and candidate offline before another attempt.
Security notes
- This is a danger step because flush ruleset removes all current nftables state.
Alternatives
- Apply on a staging clone first, or update a dedicated table without a global flush when architecture permits.
Stop conditions
- Trigger rollback immediately if administration or a critical health check fails.
decision
Test allowed and denied paths
Open a second SSH session and probe published ports externally.
Why this step matters
Tests from another host exercise the real packet path; localhost and the retained SSH session can both survive a policy that blocks every new remote connection.
What to understand
Probe SSH from the approved management CIDR and HTTPS from the public perspective. Also test at least one deliberately closed port and both address families where DNS publishes them.
A successful TCP connect proves the firewall path and listener, not application correctness. Follow with the service's own health request.
System changes
- No persistent changes; creates short external test connections and updates rule counters.
Syntax explained
nc -v- Prints the connection result.
-z- Performs a connect scan without sending application data.
&&- Runs the HTTPS port probe only after the SSH probe succeeds.
Values stay on this page and are never sent or saved.
nc -vz {{host}} {{sshPort}} && nc -vz {{host}} 443Connection to host 22 port [tcp/ssh] succeeded!
Checkpoint: Prove allowed and denied paths
nc -vz {{host}} {{sshPort}} && nc -vz {{host}} 443Continue whenApproved ports connect from their intended sources; an unapproved test port times out or is refused.
Stop whenNew SSH fails, a required service fails, or an unapproved port is reachable.
Security notes
- Run controlled probes only against the host you administer.
Alternatives
- Use curl or a protocol-specific health check after the TCP probe.
Stop conditions
- Keep the rollback timer armed until every critical external test passes.
command
Enable persistence
Enable nftables only when active and on-disk rules match.
Why this step matters
Persistence is safe only after live policy tests pass; cancel the temporary rollback last so the verified file becomes the boot-time source of truth.
What to understand
enable --now starts or confirms nftables and creates boot-time links. The active check provides immediate service evidence.
Stopping nft-rollback.timer prevents the known-good policy from unexpectedly replacing the new policy after approval. Preserve the backup for manual recovery.
System changes
- Enables nftables.service for boot and cancels the temporary rollback timer after successful validation.
Syntax explained
systemctl enable --now nftables- Enables boot loading and starts the service immediately.
systemctl is-active nftables- Confirms systemd sees the firewall service as active.
systemctl stop nft-rollback.timer- Cancels the temporary automatic restore after all tests pass.
sudo systemctl enable --now nftables && systemctl is-active nftables && sudo systemctl stop nft-rollback.timeractive Rollback timer cancelled after successful external tests
Checkpoint: Confirm persistence and timer cancellation
systemctl is-enabled nftables && systemctl is-active nftables && ! systemctl is-active --quiet nft-rollback.timerContinue whennftables is enabled and active; the rollback timer is no longer active.
Stop whenThe service is not enabled, its journal contains a load error, or tests were not completed before timer cancellation.
Security notes
- Keep the known-good backup even after cancelling the timer.
Alternatives
- Manage /etc/nftables.conf through reviewed configuration management with the same preflight and recovery gates.
Stop conditions
- Do not cancel rollback based only on the original SSH session remaining connected.
Finish line
Verification checklist
sudo nft --check --file /etc/nftables.confExit code 0.systemctl is-enabled nftablesReturns enabled.sudo nft list chain inet filter inputShows policy drop and intended accepts.Recovery guidance
Common problems and safe checks
nft --check reports an unexpected token or unknown identifier.
Likely causeA missing brace or semicolon, an undefined variable, or syntax unsupported by the installed nft version.
sudo nft --check --file /etc/nftables.confnft --versionsudo nft --debug=parser --check --file /etc/nftables.conf
ResolutionCorrect the candidate file and repeat dry validation. Do not apply only the lines that happened to parse.
The original SSH session stays open but a new SSH connection times out.
Likely causeThe management source CIDR, interface, address family or SSH port in the accept rule does not match the test client.
Use the retained session to run sudo nft -a list chain inet filter inputRun sudo ss -lntp and ip addressInspect rate-limited nft-input-drop journal messages
ResolutionRestore the saved ruleset or correct the narrow management rule, validate, apply and repeat from the real source network.
HTTPS is allowed but the application remains unreachable.
Likely causeThe service is not listening, is bound only to another address, an upstream firewall blocks it, or the request uses IPv6 while only IPv4 was checked.
sudo ss -lntpcurl --fail --include http://127.0.0.1/getent ahosts host.example.comsudo nft list counters
ResolutionFix the service or upstream network boundary rather than broadening the host firewall without evidence.
The rules work now but disappear or change after reboot.
Likely causenftables.service is disabled, /etc/nftables.conf differs from the active ruleset, or another service reloads firewall state.
systemctl is-enabled nftablessystemctl status nftables --no-pagersudo journalctl -u nftables -b --no-pager
ResolutionMake the validated persistent file authoritative, enable nftables, disable competing managers and perform a controlled reboot test.
Reference
Frequently asked questions
Why allow ICMP?
ICMP carries diagnostics and network control, including IPv6 neighbor discovery and path-MTU behavior. Blocking it indiscriminately can break normal connectivity.
Does nft --check guarantee I will not lose SSH?
No. It proves syntax, not policy intent. The backup, rollback timer, retained session and external positive tests protect against a syntactically valid lockout.
Why is the forward chain set to drop?
This baseline protects a non-routing host. Traffic transiting a VPN gateway or container host needs explicit forwarding rules in a separate design.
Recovery
Rollback
Load the saved rules from console or the scheduled fail-safe.
- Run sudo nft -f /root/nftables-before.nft.
- Restore /etc/nftables.conf.bak.
- Verify SSH and service reachability.
- Cancel the rollback timer only after the replacement is confirmed.
Evidence