Firewall policy standards — the rule as an engineering artifact
Criteria verified as of August 2026. Re-verify on the web before committing to anything (§8).
1. Scope and triggers
Applies to designing, writing, approving, deploying, reviewing and retiring filtering policy:
default-deny on ingress and egress, zone-to-zone flow matrix, rule lifecycle (who requests it,
who approves it, why it exists, when it expires), policy as code and its deployment
through CI, drift against the SoT, implementation in nftables/firewalld/ufw, network firewall
versus host firewall, stateful filtering pitfalls, ruleset review and cleanup,
deny logging, and the special cases that rewrite your rules (containers,
Kubernetes, clouds) and the chronic neglect of IPv6.
Triggers: nftables.conf, /etc/nftables.d/*.nft, nft list ruleset, nft -c -f,
table inet, hook prerouting|input|forward|output|postrouting, policy drop, ct state,
define/set/map/vmap, firewall-cmd, firewalld.conf, /etc/firewalld/zones/*.xml,
rich rule, ufw allow|deny|status, iptables-save/iptables-nft/iptables-legacy,
DOCKER-USER, daemon.json with iptables/firewall-backend, conntrack -L,
nf_conntrack_max, security group / NSG / network ACL as policy, "flow matrix",
"temporary rule", "any/any", "open a port", "rule review", "the firewall is blocking it".
Guiding principle (inherits verbatim the one in networking-standards: the network is default-deny and
documented as code; what is not in the SoT does not exist): a rule is a commitment with an
owner, a reason and an expiry date. A ruleset is the sum of live decisions, not the sediment
of old requests: if nobody knows why a rule exists, that rule is already an administrative
vulnerability, regardless of what it permits.
Not applicable: see networking-standards (parent: network design, addressing and IPAM,
VLANs and segmentation, routing/BGP, RPKI, MTU/MSS at design level, proxies and load balancers,
overlays, OOB plane — it defines the topology and the zones; which flow is permitted between
zones, under what governance and with what proof, belongs here), linux-hardening-standards (the
host firewall as a CIS/STIG baseline control and its measurement with oscap/Lynis: that the
host has default-deny on ingress, filtered egress and a versioned ruleset is their control
and is audited as part of the baseline; how that policy is designed, approved, expressed and governed
—including nftables syntax and its lifecycle— belongs here. In practice: they require and
score, this skill decides the content),
dns-standards (it exists: the DNS service, its zones and its telemetry — it defines which
resolver is legitimate and what it logs; you write and govern the rule that allows 53/853
towards that resolver, blocks outbound DNS to anything else and filters egress),
detection-engineering-standards (what is done with your logs: the detection rules, the
ECS/OCSF normalisation and the analytics on deny are theirs; the telemetry you generate —what
is logged, with which fields, at what rate and to where— is yours),
observability-standards (metrics, dashboards and alerts for the firewall as a service),
selinux-standards (MAC as an orthogonal control: network filtering does not replace process
confinement), kubernetes-standards (NetworkPolicy, CNI and in-cluster policy;
here only the interaction of kube-proxy/CNI with the node's ruleset),
container-runtime-security-standards (container isolation and escape),
aws-standards/azure-standards/gcp-standards (Security Groups, NSG, NACL and managed
firewalls as a provider service: their model, limits and IaC are theirs; the criteria of
default-deny, rule ownership and expiry belong here and apply just the same),
iac-standards (Terraform/Ansible that deploy the policy: module structure, Molecule,
lint), cicd-standards (the pipeline that validates and applies), grc-compliance-standards (the
periodic rule review as an auditable control for ENS/ISO 27001/NIS2/PCI),
incident-response-forensics-standards (the firewall log as evidence and containment
blocking during a compromise), identity-access-management-standards (bastion identity
and JIT elevation versus IP-based access), onprem-standards (platform umbrella),
homelab-standards (your own lab: the boundary is the rigour required, not the size),
bcdr-standards (ruleset restoration as part of recovery),
offensive-security-standards (offensive validation of the policy, with scope and authorisation),
ot-ics-security-standards (which conduit may exist between industrial zones is decided there
—IEC 62443-3-2 zones and conduits, level 3.5 DMZ, data diode—; here the rule that implements it is
written, approved and governed).
Also:
vpn-standards (the tunnel is theirs; the policy that filters the traffic leaving the tunnel is
yours — a wg0 that enters forward with no rules is a VPN without a firewall),
network-troubleshooting-standards (diagnosis: you set what the correct policy is and
prove that what is denied gets denied; they work out why a specific packet does not arrive —
when the symptom is "this does not connect", the answer "a rule is missing" belongs here and the answer
"state is lost through asymmetric routing" is diagnosed there and fixed here),
linux-administration-standards, ha-clustering-standards, proxmox-ve-standards.
2. Default decisions / Toolchain
Verify the latest version and its status on the project's exact distro before committing to anything
(§8): the iptables↔nftables compatibility layer changes between versions and between distros.
| Area |
Default |
Justifiable alternative |
Vetoed |
| Filtering engine on Linux |
nftables native, a single inet table with IPv4+IPv6 (upstream 1.1.6, 2025-12-05; Fedora 44 packages 1.1.4) |
firewalld (2.5.0, 2026-07-08, nftables backend) when there are dynamic zones, interfaces that come and go, or integration with NetworkManager/Podman/libvirt |
iptables-legacy; mixing iptables commands and nft rules on the same host |
| Compatibility layer |
iptables-nft only as a shim for third parties that do not yet speak nft |
Temporary coexistence, documented and with an exit date |
Writing new policy in iptables syntax |
| Simple host, single administrator |
nftables directly with a versioned file |
ufw only on a host with a trivial profile (allow 22, allow 443) |
ufw as corporate policy: it has no native nft backend, only backend_iptables.py on top of iptables-nft, and it competes for ownership of the ruleset with any other manager |
| Perimeter |
The organisation's platform (OPNsense/VyOS/appliance) governed as code — the platform choice, in networking-standards |
— |
Rules written by hand in the GUI with no reflection in the SoT |
| Source of truth |
Git repo off the device; the device is a deployment target, not the source |
Export from the device as evidence compared against the repo |
The running config as the only copy of the policy |
| Rule object |
Named sets and maps (@mgmt_nets, @web_ports) and vmap for verdict routing |
Literals only in genuinely unique rules |
Hundreds of near-identical rules that should be a set (a structural source of duplication and shadowing) |
| Application |
nft -f (atomic: either the whole ruleset applies or none of it does) from CI |
firewall-cmd --permanent + --reload |
Interactive incremental rules in production |
| Kubernetes |
kube-proxy in nftables mode (GA in 1.33, requires kernel ≥5.13) on large clusters |
iptables mode (still the upstream default, with no announced change date) |
IPVS in new deployments (deprecated in 1.35) |
| Docker |
DOCKER-USER as the policy insertion point with the iptables backend |
Experimental nftables backend (Docker Engine 29, "firewall-backend": "nftables" in daemon.json) in lab only |
DOCKER_INSECURE_NO_IPTABLES_RAW=1 in production |
| Kernel |
Pin a minimum of ≥6.18.10 / 6.19, or backports 5.15.200, 6.1.163, 6.6.124, 6.12.70 |
The distro kernel with the CVE already backported (verify, do not assume) |
A kernel without the CVE-2026-23111 patch (UAF in nf_tables, local LPE, CVSS 7.8) with unprivileged user namespaces enabled |
3. Structure and conventions
3.1 Default-deny for real: ingress and egress
policy drop on input, forward and output. A policy that only looks inwards is
half built: egress filtering is what cuts C2, exfiltration and second-stage
downloads, and it is exactly what almost nobody does because it hurts for two weeks.
- Egress by allow-list, by destination and by source: which hosts may go out, where and
to which port. Cases that must be resolved explicitly before enabling it: DNS (only towards the
corporate resolver — see
dns-standards for which one is legitimate), NTP, package and image
repositories, telemetry, ACME, outbound mail, and OS updates themselves.
- Adoption strategy without cutting the service: (1)
output in logging mode with a final rule
log prefix "EGRESS-WOULD-DROP " counter and accept; (2) analyse the log for one or two
complete business cycles (including month-end close and backup windows); (3) write the
rules with an owner; (4) flip to drop with the rescue session open. Skipping step 2
is how you break production on a Friday.
- Egress via an explicit proxy when the destination is HTTP(S): filtering by IP in the CDN era is
chasing a moving target. The proxy gives you a name, not just an address (proxy choice and deployment,
in
networking-standards).
- An exception that is not an exception: ICMP and ICMPv6. Do not block
destination-unreachable /
fragmentation needed (it kills PMTUD) nor ICMPv6 in general (it breaks ND and with it IPv6).
3.2 The lifecycle of a rule
Every rule has, in the repo, six mandatory fields; without them the PR is not approved:
| Field |
Meaning |
id |
Stable identifier, citable in tickets and in the review |
owner |
A person or team, not "infrastructure". When that person leaves, the rule is reassigned or retired |
justification |
The business or technical reason, not the description of the rule ("allows tcp/5432" is not a justification) |
requested_by / approved_by |
Who requested it and who approved it. High risk (broad egress, ingress from the Internet, any) requires security approval, not just network approval |
expires |
Mandatory expiry date. Permanent is an explicit and exceptional value, not the default |
review |
Date of the last review and its outcome |
- A rule without an owner or a date is permanent debt. The system default must be
expiry: what has to be justified is permanence, not termination.
- Rules that expire on their own: temporary vendor access, maintenance window,
debugging, migration. They are implemented with a native timeout (an nftables set element with
timeout 4h, which the kernel expires by itself) or with a CI job that rebuilds the ruleset from the
repo and leaves out whatever has expired. Always prefer the automatic mechanism: human discipline
does not revert rules at 3 in the morning.
- The "temporary any/any" that has been there for four years is the central anti-pattern of this document.
Its origin is always the same: incident + haste + "we will tighten it tomorrow" + no date. The defence
is not cultural, it is mechanical: every emergency rule is born with
expires at ≤72 h and its
renewal requires a new PR with justification. If nobody claims it when it expires, it falls away on its own —and
that is precisely the desired outcome.
- Retirement: removing a rule includes retiring it from the repo, from the device, from the flow
inventory and from the documentation. A rule "commented out just in case" is still a pending
decision; it gets deleted, that is what Git history is for.
3.3 Zone design and flow matrix
- Zones are defined by the topology (
networking-standards); the matrix belongs here: a table
source × destination where each permitted cell lists protocol, port, direction of initiation,
owner, justification and expiry. Every unlisted cell is deny.
- The matrix is the approved document; the ruleset is its compilation. It is reviewed with the business and
with security, it is versioned, and it is used as audit evidence.
- East-west microsegmentation: most of a datacenter's traffic never crosses the
perimeter. Filtering only north-south leaves lateral movement uncontrolled. Location on the network
does not confer trust (NIST SP 800-207).
- Named addresses and groups: the matrix speaks of roles (
web, db, mgmt), not of IPs.
Sets translate role→addresses and are fed from the IPAM/SoT.
3.4 Host firewall and network firewall: both
- They are not alternatives, they are layers. The network one applies policy between zones and survives
compromise of the host; the host one applies policy per service, sees traffic that never crosses
a router (same segment, same hypervisor, same node) and is the only defence against
lateral movement within a VLAN. Whoever says "the perimeter already filters that" is asserting that
their VLAN is a flat trust zone.
- The host firewall is moreover the measurable baseline control (
linux-hardening-standards):
they require it to exist and they score it; its content and governance are decided here, with the same
lifecycle, the same repo and the same gates as network policy.
- Mandatory coherence: host and network are generated from the same flow SoT. Two policies
written separately diverge within weeks and produce the worst kind of diagnosis ("it works
from here but not from there").
3.5 Stateful and its pitfalls
ct state established,related accept first, ct state invalid drop always. Without the
second, out-of-state packets traverse rules meant for new connections.
- Asymmetric routes: if the outbound and return paths go through different firewalls (or through only one in one
direction), state does not exist for the return leg and traffic drops intermittently and
irreproducibly. Classic symptom: "it works for a while and then it cuts out". It is a path design
problem, not a rule problem: it is fixed by enforcing symmetric routing or by synchronising state between the pair
(
conntrackd/pfsync), never by adding a broad accept to "make it work".
- Conntrack exhaustion:
nf_conntrack_max reached ⇒ silent drops and one line
in dmesg that nobody looks at. Monitor table occupancy as a first-class metric
(§6), size it against the box's memory and review the timeouts: tcp_timeout_established
defaults to days and keeps dead flows that hold NAT open and fill the table. A load balancer
or a server with a huge number of short connections may justify selective notrack on traffic
that does not need it —a conscious, documented decision, and never on traffic filtered by state.
- UDP and "state" are a useful fiction: conntrack infers UDP flows by timeout. Tune
UDP timeouts and consider the NAT impact for applications with long keepalives.
- MTU/MSS and fragmentation: a fragment carries no ports, so only the first one matches
L4 rules. Apply MSS clamping on
forward over tunnels (tcp flags syn tcp option maxseg size set rt mtu) — the values and the MTU design, in networking-standards. A firewall that
simply drops fragments breaks large DNS, IPsec and VPN.
- NAT and filtering are evaluated at different moments: DNAT happens in
prerouting, before
forward, so that the filtering rules see the already translated internal IP, not the
published one. Writing the rule against the public IP is the rookie mistake that opens what you thought you were
closing. The order: prerouting translates, forward decides. And SNAT/masquerade filters nothing:
hiding is not protecting, and with IPv6 that illusion disappears.
3.6 Reference skeleton (nftables, single inet table)
# SoT: repo/firewall/base.nft — deployed by CI with `nft -f` (atomic). Do not edit on the host.
table inet policy {
set mgmt_nets { type ipv4_addr; flags interval; elements = { 10.0.10.0/24 } }
set mgmt_nets6 { type ipv6_addr; flags interval; elements = { 2001:db8:10::/64 } }
set temp_access { type ipv4_addr; flags timeout; } # added with `timeout`, they expire on their own
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif lo accept
ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded } accept
icmpv6 type { nd-neighbor-solicit, nd-neighbor-advert, nd-router-advert,
echo-request, packet-too-big, time-exceeded, parameter-problem } accept
# id=FW-001 owner=plataforma expires=2027-01-31 just="administration via bastion"
ip saddr @mgmt_nets tcp dport 22 accept
ip6 saddr @mgmt_nets6 tcp dport 22 accept
limit rate 10/second burst 20 packets log prefix "IN-DENY " level info counter
}
chain forward {
type filter hook forward priority filter; policy drop;
tcp flags syn tcp option maxseg size set rt mtu # MSS clamping on tunnels
ct state established,related accept
ct state invalid drop
limit rate 10/second burst 20 packets log prefix "FWD-DENY " level info counter
}
chain output {
type filter hook output priority filter; policy drop; # egress filtered, not decorative
ct state established,related accept
# id=FW-010 owner=plataforma expires=permanent just="internal resolution (see dns-standards)"
ip daddr @resolvers udp dport 53 accept
ip daddr @resolvers tcp dport { 53, 853 } accept
limit rate 10/second burst 20 packets log prefix "OUT-DENY " level info counter
}
}
Notes on form that are criteria, not style: a single inet table (IPv4 and IPv6 in the same
rules avoids divergence); a comment with id, owner, expires and justification on every
permit rule; named sets instead of repeated literals; counter on the rules
you want to be able to audit by usage; and the deny log always rate-limited.
4. Mandatory quality gates
In order of increasing cost. Gates 1, 2, 3 and 5 are automatable and break the build or the
deployment. Gate 4 is not a gate: it is a hard operational precondition — nothing in a pipeline
can assert that a rescue session is open, so it blocks the human making the change, not the merge.
Do not implement it as CI and do not report it as automated coverage.
- Syntax validation:
nft -c -f ruleset.nft (and firewall-cmd --check-config where
applicable) on every PR. A policy that does not validate does not even reach staging.
- Policy lint in CI, failing hard:
- No base chain without
policy drop.
- No permit rule without
id, owner, justification and expires.
- No expired rule (
expires in the past) and a warning at 30 days.
- No
any/0.0.0.0/0/::/0 in source and destination at the same time without the approved
exception tag.
- IPv6 parity: every IPv4 rule has its IPv6 equivalent or a justified exemption. This
gate exists because forgetting IPv6 is the most common failure in the trade: the service listens
on
::, the attacker arrives over IPv6 and the policy only covered IPv4.
- No orphaned rule: every
id exists in the approved flow matrix.
- Testing in an equivalent environment before production: apply the ruleset on a twin host or VM
and run the connectivity test suite. New topology or protocol changes,
in a lab (containerlab) before touching hardware.
- Application with a safety net — no exceptions. Every remote firewall change is made
with a rescue session open or an OOB console available, and with timed rollback:
commit-confirm (VyOS), safe mode (RouterOS), or on Linux an at/systemd-run --on-active
that restores the previous ruleset in N minutes unless explicitly confirmed. Without that, do not
touch it. Locking yourself out of the firewall is the most predictable and most avoidable incident there is.
- Mandatory negative testing: verify that what is permitted works and that what is denied gets
denied, from the real source and against the real destination. A port sweep from one zone
towards another confirming that only the expected things respond (with internal authorisation;
offensive-security-standards for formal validation). A firewall tested only along the
happy path is not tested: you do not know whether your rule works or whether the service was already
down.
- Drift detection: periodic and automatic comparison between
nft list ruleset (or the
device export) and the artifact generated from the repo. Every difference is a finding
with an owner. Drift is the metric of whether your governance is real or theatre.
- Periodic ruleset review (quarterly at the perimeter, twice-yearly internally) producing
a report with: expired rules, rules without an owner, unused ones (counter at zero for the whole
period), shadowed ones (never reached because of an earlier rule), duplicate ones,
redundant ones (a subset of another), generalised ones and excessively permissive ones. Each
finding is closed with an action, not with a "reviewed". This report is direct evidence for
grc-compliance-standards.
- Reachability verification before merge in complex topologies (several firewalls in the
path): model the change and check what it really opens and closes, including the rules of
the other devices along the path. It is the only way to detect shadowing between boxes.
5. Security
5.1 Logging and observability of the policy
deny events are logged, and they matter more than the allow ones. An expected allow tells you
nothing; a repeated denial is either an attack, or an uncommunicated change, or a missing
rule. All three cases require action.
- Format and fields: a stable prefix identifiable per chain (
IN-DENY, FWD-DENY,
OUT-DENY), with source/destination IP, ports, protocol, interface and timestamp. The prefix is a
contract: detection-engineering-standards builds rules on top of it and breaking it breaks their
detections.
- Volume and cost are a design problem, not an accident: a
log without limit in an Internet-facing
drop chain is a self-denial of service and a SIEM bill. Always use
limit rate, aggregate with counter whatever you only need to count, and decide what is sent
to the SIEM and what stays in cheap storage. Logging everything at the same priority amounts to
logging nothing.
- Denied egress is the golden signal: an internal host trying to reach a destination that is not
permitted is, almost always, either misconfigured software or something that should not be there. That
signal only exists if there is an egress policy.
- Per-rule counters as the source for the unused-rule review (§4.7) and for
sizing. Without counters, "this rule is no longer needed" is an opinion.
5.2 Containers: the classic of publishing a port and bypassing the firewall
- The failure: when publishing a port (
-p 5432:5432), Docker inserts its own NAT rules
in prerouting and forwarding rules in forward. Inbound traffic towards the container never
traverses input, which is where ufw's policy and that of most host rulesets live.
Result: "the port is blocked" and "the port is open" are true at the same time, and the
service ends up exposed to the network while you believe otherwise. Risk class: unintended
exposure through a bypass of the filtering chain, not a product vulnerability.
- Mitigations, in order of preference:
- Do not publish what must not be public: explicit bind to loopback (
127.0.0.1:5432:5432)
and a reverse proxy as the only surface. Most of these incidents are solved here.
- Policy in
DOCKER-USER, which Docker evaluates before its own accept
rules: it is the supported insertion point with the iptables backend, and it survives
daemon restarts and container recreation.
internal networks for whatever must not go out, and expose instead of publish when
container-to-container communication is enough.
- Persist your rules with a systemd unit that runs after Docker; verify
with a scan from another machine, never from the host itself.
- What has changed (verified Aug 2026): Docker Engine 28.0 hardened the default
behaviour — unsolicited inbound traffic towards a container's internal IP is
dropped unless the port has been explicitly published, closing the case of containers
reachable from the LAN with
FORWARD set to ACCEPT. There is still an input bypass for
the ports you do publish: the fundamental pattern has not gone away.
DOCKER_INSECURE_NO_IPTABLES_RAW=1 disables raw table rules and reopens part of that
hardening (including the protection of ports published on 127.0.0.1): vetoed in production.
- Docker Engine 29 introduces an experimental nftables backend
(
"firewall-backend": "nftables"), which creates its own ip docker-bridges /
ip6 docker-bridges tables, does not create a DOCKER-USER chain and does not support Swarm. Operational
translation: if you enable it, your policy insertion point changes and your current rules stop
applying. Lab only until it stops being experimental.
- Distribution trap (Debian 13 and equivalents): a container whose image ships
iptables-legacy writing rules while the host uses iptables-nft leaves those rules in
tables the kernel does not consult. They fail silently: the operator believes they have filtered.
- Kubernetes:
kube-proxy and the CNI generate and regenerate their own chains on the node. Do not
edit their chains; write your policy in your own tables/priorities that are evaluated earlier, and
use NetworkPolicy for what is pod policy (kubernetes-standards). kube-proxy in
nftables mode has been GA since 1.33 but iptables is still the default, and both modes
coexist across a heterogeneous fleet: your node ruleset must tolerate both.
- Podman/netavark: the nftables driver is the supported path and the iptables one is being retired;
verify which is active before writing rules around it.
5.3 Clouds: security groups as the logical equivalent
- Security Groups, NSG and NACL are filtering policy and everything in this document applies to them:
default-deny, owner, justification, expiry, periodic review, IPv6 parity and deployment by
code. The specific model and its limits, in
aws-standards/azure-standards/gcp-standards.
- Differences that change the design and that must be kept in mind: SGs are usually stateful and
allow-only (there is no "explicit deny", which eliminates shadowing but also the
ability to make exceptions), NACLs are stateless (you have to open the return path and the ephemeral
ports by hand, a common mistake), and there are hard rule limits per group that push you to
group by role —which is, moreover, the correct design.
- The SG does not replace the host firewall: within the same group, traffic is usually
permitted implicitly. And
0.0.0.0/0 in an SG is exactly the same finding as in
a physical firewall.
5.4 Management plane
- Firewall administration arrives only from the OOB/bastion network, with MFA and named
accounts; the firewall itself does not expose its management plane to user networks or to the Internet
(OOB design in
networking-standards, identity in
identity-access-management-standards).
- Golden anti-lockout rule: the rule that permits your management access is the first one
written, the last one touched and the one that never depends on a change in flight.
- Every change is attributed to a person: applied by CI from a signed PR. A
change applied by hand on the device is, by definition, a change with no verifiable author.
- Kernel and engine up to date: netfilter itself is surface. CVE-2026-23111 (UAF in
nf_tables, local escalation to root via user namespaces, CVSS 7.8, published 2026-02-13) requires
kernel ≥6.18.10/6.19 or the backports 5.15.200 / 6.1.163 / 6.6.124 / 6.12.70. Complementary
mitigation while patching: restrict unprivileged user namespaces and access to
CAP_NET_ADMIN (baseline detail in linux-hardening-standards).
6. Performance and operability
- First-class metrics: conntrack table occupancy against
nf_conntrack_max
(alert at 70-80%: it is a silent outage announced in advance), packet drop rate per chain,
per-rule counters, network softirq CPU, and added latency along the path. Collection,
thresholds and alerts, in observability-standards.
- The cost of a badly written ruleset: evaluation is linear per chain. Thousands of sequential
rules where a set or a map would resolve in constant time is a performance
and maintainability problem. nftables sets and maps are not syntactic sugar: they are the
difference between reviewing 40 rules and reviewing 4,000.
- Order by frequency, not by aesthetics:
ct state established,related accept first,
always; the most common, earlier; the exceptional, later. And with vmap instead of long chains
of comparisons when the criterion is a discrete value.
- HA: a firewall pair with state synchronisation (
conntrackd/pfsync/VRRP) and exercised
failover. Without synchronisation, every failover cuts all active sessions —sometimes that is
acceptable, but it must be a decision, not a surprise.
- Recovery: the ruleset is restored from the repo onto a clean box in minutes, and that is
tested (
bcdr-standards). The backup of the device configuration is evidence, not
a source.
- Runbooks with an owner: accidental lockout of management access, conntrack exhausted, loss of one
node of the HA pair, an expired rule that cuts a service in production, legitimate traffic denied
after a deployment, emergency opening (with its ≤72 h expiry already included in the
template).
7. Sustainability and prohibitions
- Cadence: rule review quarterly at the perimeter and twice-yearly internally (§4.7); review
of the engine version (nftables/firewalld) and of kernel/netfilter CVEs monthly; migration of everything
still left in iptables syntax with a written exit date.
- Continuous simplification: each review must reduce the number of rules or justify why
it grows. A ruleset that only grows is a ruleset nobody understands any more, and a ruleset nobody
understands cannot be audited or changed safely.
- Single ownership of the ruleset: one manager per host.
ufw + firewalld + your own nft
rules + Docker fighting over the same ruleset produces unpredictable effective policy. Decide
who is in charge and disable the rest explicitly.
FORBIDDEN
- ❌ A base chain without
policy drop; "default-allow and we will close things down as we go".
- ❌ A firewall without egress filtering; "it is an internal network, no need to filter outbound".
- ❌ A rule without an owner, without a business justification or without an expiry date.
- ❌
any/any (or 0.0.0.0/0↔::/0) "temporary" without expires and without security approval.
- ❌ Changing the firewall remotely without a rescue session, an OOB console or timed rollback.
- ❌ Accepting a policy as good without negative proof that what is denied gets denied.
- ❌ Editing rules by hand on the device instead of through PR + CI; leaving drift uncorrected.
- ❌ The running configuration as the source of truth for the policy.
- ❌ Mixing
iptables and nftables on the same host; writing new policy in iptables syntax;
iptables-legacy on new systems.
- ❌ Several managers competing for the ruleset (
ufw + firewalld + nft + Docker).
- ❌ IPv4 policy without its IPv6 equivalent (the most common and most exploited omission).
- ❌ Blocking ICMP indiscriminately (it kills PMTUD and diagnosis) or ICMPv6 (it breaks ND).
- ❌
log without limit rate in exposed drop chains.
- ❌ Not logging denials, or not forwarding them to detection engineering.
- ❌ Filtering against the public IP in rules that are evaluated after DNAT.
- ❌ Ignoring
ct state invalid, or adding a broad accept to paper over an asymmetric route.
- ❌ Not monitoring conntrack occupancy.
- ❌ Publishing container ports without checking the real exposure from another machine.
- ❌
DOCKER_INSECURE_NO_IPTABLES_RAW=1 in production; editing by hand the Docker,
kube-proxy or CNI chains.
- ❌ Treating cloud Security Groups as something other than firewall policy.
- ❌ Relying on NAT/masquerade as a security control.
- ❌ A firewall management plane reachable from user networks or from the Internet.
- ❌ A kernel without the CVE-2026-23111 patch on multi-user hosts or hosts with untrusted containers.
- ❌ Hundreds of near-identical rules where a set or a map was the right answer.
8. Mandatory web verification
Before pinning any version, behaviour or limit, look it up — do not recall it.
Verified Aug 2026:
- nftables: latest upstream 1.1.6 (2025-12-05); previous 1.1.5 (2025-08-27) and
1.1.4 (2025-08-06). firewalld 2.5.0 (2026-07-08), nftables backend.
- iptables→nftables migration: nftables is the default framework across all major
distros;
iptables-nft still exists as a shim in RHEL 9/10, Debian 13 (trixie) and
Ubuntu — its removal has not been confirmed in any of them, only its deprecation.
Verify on the project's exact distro. Confirmed trap: containers with
iptables-legacy on an iptables-nft host write rules the kernel ignores.
- ufw: it has no native nftables backend; only
backend_iptables.py on top of iptables-nft.
The maintainer states ongoing maintenance but no priority for backend_nft.py.
- Docker: Engine 28.0 drops unsolicited inbound traffic to container IPs unless the
port is published; the
input bypass for published ports persists;
DOCKER_INSECURE_NO_IPTABLES_RAW=1 discouraged. Engine 29 adds
"firewall-backend": "nftables" as experimental (tables ip docker-bridges/ip6 docker-bridges, no DOCKER-USER, no Swarm).
- Kubernetes:
kube-proxy nftables mode GA in 1.33, requires kernel ≥5.13, is not the
default (iptables still is, with no announced change date); IPVS deprecated in 1.35.
- CVE: CVE-2026-23111 — UAF in
nf_tables (nft_map_catchall_activate(), inverted
check), local LPE via user namespaces, CVSS 7.8 (AV:L/AC:L/PR:L/UI:N/C:H/I:H/A:H),
published 2026-02-13. Patched in ≥6.18.10 and 6.19, backports 5.15.200, 6.1.163,
6.6.124, 6.12.70. Re-verify the current month's netfilter CVEs before pinning a minimum.
Declared gaps — do NOT fill from memory, verify before use:
- nftables version packaged by distro: only 1.1.4 on Fedora 44 is on record (inherited from
networking-standards, not re-verified). The versions in RHEL 10, Debian 13 and Ubuntu LTS
are not verified.
- Specific
ufw version current in 2026: not obtained. Check with ufw --version
or on the distro's package page.
- Ruleset auditing tools:
audit-springbok exists (anomaly taxonomy:
shadowing, redundancy, generalisation, correlation) and Batfish for pre-merge reachability
verification, but no native shadowing/redundancy analyser has been found
for nftables, and the maintenance status and current version of both tools are not
verified. Do not recommend them as products without checking; the anomaly taxonomy in
§4.7 is valid as a criterion.
- Detail of the Security Groups/NSG/NACL model (rule limits, exact stateful semantics,
IPv6 support): described at the level of criteria, not verified against the current
documentation of each provider. Cross-check with
aws-standards/azure-standards/gcp-standards.
- Status of the iptables driver in Podman/netavark (deprecated or already removed?, in which version):
only a maintainer's declared intent is on record. Verify before asserting it.
- Exact syntax and availability of
--check-config in firewalld 2.5.0: cited from memory,
not verified.
- Status of conntrackd/pfsync and of state synchronisation options on the specific
perimeter platforms: not verified in this pass.
If the web contradicts this document, the web wins — flag the discrepancy.
1---2name: firewall-policy-standards3description: Firewall policy as a governed engineering artifact. Use when writing or reviewing nftables rulesets (nftables.conf, nft -c -f, tables/chains/hooks/priorities, sets, maps, verdict maps, ct state, meters), firewalld zones, services, policies and rich rules (firewall-cmd), ufw profiles, DOCKER-USER chains and Docker firewall-backend published-port bypass, kube-proxy nftables mode, cloud security group and NSG rule sets as filtering policy, egress allow-listing, zone-to-zone flow matrices, rule ownership, expiry dates and change approval, shadowed, duplicate, orphaned or any/any rule review, conntrack table exhaustion and asymmetric-routing state loss, MSS clamping and NAT interaction with filtering, deny logging volume and forwarding, or IPv6 rule parity with IPv4.4---56# Firewall policy standards — the rule as an engineering artifact78Criteria verified as of **August 2026**. Re-verify on the web before committing to anything (§8).910## 1. Scope and triggers1112Applies to **designing, writing, approving, deploying, reviewing and retiring filtering policy**:13default-deny on ingress and egress, zone-to-zone flow matrix, rule lifecycle (who requests it,14who approves it, why it exists, when it expires), policy as code and its deployment15through CI, drift against the SoT, implementation in `nftables`/`firewalld`/`ufw`, network firewall16versus host firewall, *stateful* filtering pitfalls, ruleset review and cleanup,17deny logging, and the special cases that rewrite your rules (containers,18Kubernetes, clouds) and the chronic neglect of IPv6.1920Triggers: `nftables.conf`, `/etc/nftables.d/*.nft`, `nft list ruleset`, `nft -c -f`,21`table inet`, `hook prerouting|input|forward|output|postrouting`, `policy drop`, `ct state`,22`define`/`set`/`map`/`vmap`, `firewall-cmd`, `firewalld.conf`, `/etc/firewalld/zones/*.xml`,23`rich rule`, `ufw allow|deny|status`, `iptables-save`/`iptables-nft`/`iptables-legacy`,24`DOCKER-USER`, `daemon.json` with `iptables`/`firewall-backend`, `conntrack -L`,25`nf_conntrack_max`, `security group` / `NSG` / `network ACL` as policy, "flow matrix",26"temporary rule", "any/any", "open a port", "rule review", "the firewall is blocking it".2728**Guiding principle** (inherits verbatim the one in `networking-standards`: *the network is default-deny and29documented as code; what is not in the SoT does not exist*): **a rule is a commitment with an30owner, a reason and an expiry date**. A ruleset is the sum of live decisions, not the sediment31of old requests: if nobody knows why a rule exists, that rule is already an administrative32vulnerability, regardless of what it permits.3334**Not applicable**: see `networking-standards` (**parent**: network design, addressing and IPAM,35VLANs and segmentation, routing/BGP, RPKI, MTU/MSS at design level, proxies and load balancers,36overlays, OOB plane — **it defines the topology and the zones; which flow is permitted between37zones, under what governance and with what proof, belongs here**), `linux-hardening-standards` (**the38*host* firewall as a CIS/STIG baseline control and its measurement with `oscap`/Lynis**: that the39host has default-deny on ingress, filtered egress and a versioned ruleset is **their** control40and is audited as part of the baseline; **how that policy is designed, approved, expressed and governed41—including nftables syntax and its lifecycle— belongs here**. In practice: they require and42score, this skill decides the content),43`dns-standards` (**it exists**: the DNS service, its zones and its telemetry — **it defines which44resolver is legitimate and what it logs; you write and govern the rule** that allows `53`/`853`45towards that resolver, blocks outbound DNS to anything else and filters egress),46`detection-engineering-standards` (**what is done with your logs**: the detection rules, the47ECS/OCSF normalisation and the analytics on `deny` are theirs; **the telemetry you generate —what48is logged, with which fields, at what rate and to where— is yours**),49`observability-standards` (metrics, dashboards and alerts for the firewall as a service),50`selinux-standards` (MAC as an orthogonal control: network filtering does not replace process51confinement), `kubernetes-standards` (`NetworkPolicy`, CNI and in-cluster policy;52**here only the interaction of kube-proxy/CNI with the node's ruleset**),53`container-runtime-security-standards` (container isolation and escape),54`aws-standards`/`azure-standards`/`gcp-standards` (Security Groups, NSG, NACL and managed55firewalls as a **provider service**: their model, limits and IaC are theirs; **the criteria of56default-deny, rule ownership and expiry belong here and apply just the same**),57`iac-standards` (Terraform/Ansible that deploy the policy: module structure, Molecule,58lint), `cicd-standards` (the pipeline that validates and applies), `grc-compliance-standards` (the59periodic rule review as an auditable control for ENS/ISO 27001/NIS2/PCI),60`incident-response-forensics-standards` (the firewall log as evidence and containment61blocking during a compromise), `identity-access-management-standards` (bastion identity62and JIT elevation versus IP-based access), `onprem-standards` (platform umbrella),63`homelab-standards` (your own lab: the boundary is the rigour required, not the size),64`bcdr-standards` (ruleset restoration as part of recovery),65`offensive-security-standards` (offensive validation of the policy, with scope and authorisation),66`ot-ics-security-standards` (**which conduit may exist between industrial zones is decided there**67—IEC 62443-3-2 zones and conduits, level 3.5 DMZ, data diode—; **here the rule that implements it is68written, approved and governed**).6970Also:71`vpn-standards` (**the tunnel is theirs; the policy that filters the traffic leaving the tunnel is72yours** — a `wg0` that enters `forward` with no rules is a VPN without a firewall),73`network-troubleshooting-standards` (**diagnosis**: you set what the correct policy is and74prove that what is denied gets denied; they work out **why** a specific packet does not arrive —75when the symptom is "this does not connect", the answer "a rule is missing" belongs here and the answer76"state is lost through asymmetric routing" is diagnosed there and fixed here),77`linux-administration-standards`, `ha-clustering-standards`, `proxmox-ve-standards`.7879## 2. Default decisions / Toolchain8081> Verify the latest version and its status on the project's **exact distro** before committing to anything82> (§8): the `iptables`↔`nftables` compatibility layer changes between versions and between distros.8384| Area | Default | Justifiable alternative | Vetoed |85|---|---|---|---|86| Filtering engine on Linux | **nftables** native, a single `inet` table with IPv4+IPv6 (upstream 1.1.6, 2025-12-05; Fedora 44 packages 1.1.4) | `firewalld` (**2.5.0**, 2026-07-08, nftables backend) when there are dynamic zones, interfaces that come and go, or integration with NetworkManager/Podman/libvirt | `iptables-legacy`; **mixing** `iptables` commands and `nft` rules on the same host |87| Compatibility layer | `iptables-nft` **only as a shim for third parties** that do not yet speak nft | Temporary coexistence, documented and with an exit date | Writing new policy in iptables syntax |88| Simple host, single administrator | **nftables directly** with a versioned file | `ufw` only on a host with a trivial profile (`allow 22`, `allow 443`) | `ufw` as corporate policy: **it has no native nft backend**, only `backend_iptables.py` on top of `iptables-nft`, and it competes for ownership of the ruleset with any other manager |89| Perimeter | The organisation's platform (OPNsense/VyOS/appliance) governed as code — the platform choice, in `networking-standards` | — | Rules written by hand in the GUI with no reflection in the SoT |90| Source of truth | **Git repo off the device**; the device is a deployment target, not the source | Export from the device as *evidence* compared against the repo | The running config as the only copy of the policy |91| Rule object | **Named sets and maps** (`@mgmt_nets`, `@web_ports`) and `vmap` for verdict routing | Literals only in genuinely unique rules | Hundreds of near-identical rules that should be a set (a structural source of duplication and shadowing) |92| Application | `nft -f` (**atomic**: either the whole ruleset applies or none of it does) from CI | `firewall-cmd --permanent` + `--reload` | Interactive incremental rules in production |93| Kubernetes | `kube-proxy` in **nftables** mode (GA in 1.33, requires kernel ≥5.13) on large clusters | `iptables` mode (**still the upstream default**, with no announced change date) | `IPVS` in new deployments (**deprecated in 1.35**) |94| Docker | `DOCKER-USER` as the policy insertion point with the iptables backend | **Experimental nftables backend** (Docker Engine 29, `"firewall-backend": "nftables"` in `daemon.json`) in lab only | `DOCKER_INSECURE_NO_IPTABLES_RAW=1` in production |95| Kernel | Pin a minimum of **≥6.18.10 / 6.19**, or backports **5.15.200, 6.1.163, 6.6.124, 6.12.70** | The distro kernel with the CVE already backported (verify, do not assume) | A kernel without the **CVE-2026-23111** patch (UAF in `nf_tables`, local LPE, CVSS 7.8) with unprivileged user namespaces enabled |9697## 3. Structure and conventions9899### 3.1 Default-deny for real: ingress **and** egress100101- `policy drop` on `input`, `forward` **and `output`**. A policy that only looks inwards is102 half built: **egress filtering is what cuts C2, exfiltration and second-stage103 downloads**, and it is exactly what almost nobody does because it hurts for two weeks.104- **Egress by allow-list, by destination and by source**: which hosts may go out, where and105 to which port. Cases that must be resolved explicitly before enabling it: DNS (only towards the106 corporate resolver — see `dns-standards` for which one is legitimate), NTP, package and image107 repositories, telemetry, ACME, outbound mail, and OS updates themselves.108- **Adoption strategy without cutting the service**: (1) `output` in logging mode with a final rule109 `log prefix "EGRESS-WOULD-DROP " counter` and `accept`; (2) analyse the log for one or two110 complete business cycles (including month-end close and backup windows); (3) write the111 rules with an owner; (4) flip to `drop` with the rescue session open. **Skipping step 2112 is how you break production on a Friday.**113- Egress via an **explicit proxy** when the destination is HTTP(S): filtering by IP in the CDN era is114 chasing a moving target. The proxy gives you a name, not just an address (proxy choice and deployment,115 in `networking-standards`).116- **An exception that is not an exception**: ICMP and ICMPv6. Do not block `destination-unreachable` /117 `fragmentation needed` (it kills PMTUD) nor ICMPv6 in general (it breaks ND and with it IPv6).118119### 3.2 The lifecycle of a rule120121Every rule has, in the repo, **six mandatory fields**; without them the PR is not approved:122123| Field | Meaning |124|---|---|125| `id` | Stable identifier, citable in tickets and in the review |126| `owner` | **A person or team**, not "infrastructure". When that person leaves, the rule is reassigned or retired |127| `justification` | The **business or technical reason**, not the description of the rule ("allows tcp/5432" is not a justification) |128| `requested_by` / `approved_by` | Who requested it and who approved it. High risk (broad egress, ingress from the Internet, `any`) requires security approval, not just network approval |129| `expires` | **Mandatory expiry date.** Permanent is an explicit and exceptional value, not the default |130| `review` | Date of the last review and its outcome |131132- **A rule without an owner or a date is permanent debt.** The system default must be133 expiry: what has to be justified is permanence, not termination.134- **Rules that expire on their own**: temporary vendor access, maintenance window,135 debugging, migration. They are implemented with a **native timeout** (an nftables set element with136 `timeout 4h`, which the kernel expires by itself) or with a CI job that rebuilds the ruleset from the137 repo and leaves out whatever has expired. **Always** prefer the automatic mechanism: human discipline138 does not revert rules at 3 in the morning.139- **The "temporary any/any" that has been there for four years** is the central anti-pattern of this document.140 Its origin is always the same: incident + haste + "we will tighten it tomorrow" + no date. The defence141 is not cultural, it is mechanical: **every emergency rule is born with `expires` at ≤72 h** and its142 renewal requires a new PR with justification. If nobody claims it when it expires, it falls away on its own —and143 that is precisely the desired outcome.144- **Retirement**: removing a rule includes retiring it from the repo, from the device, from the flow145 inventory and from the documentation. A rule "commented out just in case" is still a pending146 decision; it gets deleted, that is what Git history is for.147148### 3.3 Zone design and flow matrix149150- Zones are defined by the topology (`networking-standards`); **the matrix belongs here**: a table151 source × destination where each permitted cell lists protocol, port, direction of initiation,152 owner, justification and expiry. Every unlisted cell is `deny`.153- **The matrix is the approved document**; the ruleset is its compilation. It is reviewed with the business and154 with security, it is versioned, and it is used as audit evidence.155- **East-west microsegmentation**: most of a datacenter's traffic never crosses the156 perimeter. Filtering only north-south leaves lateral movement uncontrolled. Location on the network157 does not confer trust (NIST SP 800-207).158- **Named addresses and groups**: the matrix speaks of roles (`web`, `db`, `mgmt`), not of IPs.159 Sets translate role→addresses and are fed from the IPAM/SoT.160161### 3.4 Host firewall **and** network firewall: both162163- **They are not alternatives, they are layers.** The network one applies policy between zones and survives164 compromise of the host; the host one applies policy **per service**, sees traffic that never crosses165 a router (same segment, same hypervisor, same node) and is the only defence against166 lateral movement within a VLAN. Whoever says "the perimeter already filters that" is asserting that167 their VLAN is a flat trust zone.168- The host firewall is moreover **the measurable baseline control** (`linux-hardening-standards`):169 they require it to exist and they score it; **its content and governance are decided here**, with the same170 lifecycle, the same repo and the same gates as network policy.171- **Mandatory coherence**: host and network are generated from the **same** flow SoT. Two policies172 written separately diverge within weeks and produce the worst kind of diagnosis ("it works173 from here but not from there").174175### 3.5 Stateful and its pitfalls176177- **`ct state established,related accept` first, `ct state invalid drop` always.** Without the178 second, out-of-state packets traverse rules meant for new connections.179- **Asymmetric routes**: if the outbound and return paths go through different firewalls (or through only one in one180 direction), state does not exist for the return leg and traffic drops **intermittently and181 irreproducibly**. Classic symptom: "it works for a while and then it cuts out". It is a path design182 problem, not a rule problem: it is fixed by enforcing symmetric routing or by synchronising state between the pair183 (`conntrackd`/pfsync), never by adding a broad `accept` to "make it work".184- **Conntrack exhaustion**: `nf_conntrack_max` reached ⇒ silent drops and one line185 in `dmesg` that nobody looks at. **Monitor table occupancy as a first-class metric**186 (§6), size it against the box's memory and review the timeouts: `tcp_timeout_established`187 defaults to days and keeps dead flows that hold NAT open and fill the table. A load balancer188 or a server with a huge number of short connections may justify selective `notrack` on traffic189 that does not need it —a conscious, documented decision, and never on traffic filtered by state.190- **UDP and "state" are a useful fiction**: conntrack infers UDP flows by timeout. Tune191 UDP timeouts and consider the NAT impact for applications with long keepalives.192- **MTU/MSS and fragmentation**: a fragment carries no ports, so only the first one matches193 L4 rules. Apply **MSS clamping** on `forward` over tunnels (`tcp flags syn tcp option maxseg194 size set rt mtu`) — the values and the MTU design, in `networking-standards`. A firewall that195 simply drops fragments breaks large DNS, IPsec and VPN.196- **NAT and filtering are evaluated at different moments**: DNAT happens in `prerouting`, before197 `forward`, so that the filtering rules see the **already translated internal IP**, not the198 published one. Writing the rule against the public IP is the rookie mistake that opens what you thought you were199 closing. The order: `prerouting` translates, `forward` decides. And SNAT/masquerade filters nothing:200 hiding is not protecting, and with IPv6 that illusion disappears.201202### 3.6 Reference skeleton (nftables, single `inet` table)203204```nft205# SoT: repo/firewall/base.nft — deployed by CI with `nft -f` (atomic). Do not edit on the host.206table inet policy {207 set mgmt_nets { type ipv4_addr; flags interval; elements = { 10.0.10.0/24 } }208 set mgmt_nets6 { type ipv6_addr; flags interval; elements = { 2001:db8:10::/64 } }209 set temp_access { type ipv4_addr; flags timeout; } # added with `timeout`, they expire on their own210211 chain input {212 type filter hook input priority filter; policy drop;213 ct state established,related accept214 ct state invalid drop215 iif lo accept216 ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded } accept217 icmpv6 type { nd-neighbor-solicit, nd-neighbor-advert, nd-router-advert,218 echo-request, packet-too-big, time-exceeded, parameter-problem } accept219 # id=FW-001 owner=plataforma expires=2027-01-31 just="administration via bastion"220 ip saddr @mgmt_nets tcp dport 22 accept221 ip6 saddr @mgmt_nets6 tcp dport 22 accept222 limit rate 10/second burst 20 packets log prefix "IN-DENY " level info counter223 }224225 chain forward {226 type filter hook forward priority filter; policy drop;227 tcp flags syn tcp option maxseg size set rt mtu # MSS clamping on tunnels228 ct state established,related accept229 ct state invalid drop230 limit rate 10/second burst 20 packets log prefix "FWD-DENY " level info counter231 }232233 chain output {234 type filter hook output priority filter; policy drop; # egress filtered, not decorative235 ct state established,related accept236 # id=FW-010 owner=plataforma expires=permanent just="internal resolution (see dns-standards)"237 ip daddr @resolvers udp dport 53 accept238 ip daddr @resolvers tcp dport { 53, 853 } accept239 limit rate 10/second burst 20 packets log prefix "OUT-DENY " level info counter240 }241}242```243244Notes on form that are criteria, not style: **a single `inet` table** (IPv4 and IPv6 in the same245rules avoids divergence); **a comment with `id`, `owner`, `expires` and justification on every246permit rule**; **named sets** instead of repeated literals; `counter` on the rules247you want to be able to audit by usage; and the deny `log` **always rate-limited**.248249## 4. Mandatory quality gates250251In order of increasing cost. **Gates 1, 2, 3 and 5 are automatable and break the build or the252deployment. Gate 4 is not a gate: it is a hard operational precondition** — nothing in a pipeline253can assert that a rescue session is open, so it blocks the human making the change, not the merge.254Do not implement it as CI and do not report it as automated coverage.2552561. **Syntax validation**: `nft -c -f ruleset.nft` (and `firewall-cmd --check-config` where257 applicable) on every PR. A policy that does not validate does not even reach staging.2582. **Policy lint** in CI, failing hard:259 - No base chain without `policy drop`.260 - No permit rule without `id`, `owner`, `justification` and `expires`.261 - **No expired rule** (`expires` in the past) and a warning at 30 days.262 - No `any`/`0.0.0.0/0`/`::/0` in source **and** destination at the same time without the approved263 exception tag.264 - **IPv6 parity**: every IPv4 rule has its IPv6 equivalent or a justified exemption. This265 gate exists because **forgetting IPv6 is the most common failure in the trade**: the service listens266 on `::`, the attacker arrives over IPv6 and the policy only covered IPv4.267 - No orphaned rule: every `id` exists in the approved flow matrix.2683. **Testing in an equivalent environment before production**: apply the ruleset on a twin host or VM269 and run the connectivity test suite. New topology or protocol changes,270 in a lab (containerlab) before touching hardware.2714. **Application with a safety net — no exceptions.** Every remote firewall change is made272 with a **rescue session open or an OOB console available**, and with timed rollback:273 `commit-confirm` (VyOS), safe mode (RouterOS), or on Linux an `at`/`systemd-run --on-active`274 that restores the previous ruleset in N minutes unless explicitly confirmed. Without that, **do not275 touch it**. Locking yourself out of the firewall is the most predictable and most avoidable incident there is.2765. **Mandatory negative testing**: verify that what is permitted works **and that what is denied gets277 denied**, from the real source and against the real destination. A port sweep from one zone278 towards another confirming that only the expected things respond (with internal authorisation;279 `offensive-security-standards` for formal validation). A firewall tested only along the280 happy path **is not tested**: you do not know whether your rule works or whether the service was already281 down.2826. **Drift detection**: periodic and automatic comparison between `nft list ruleset` (or the283 device export) and the artifact generated from the repo. Every difference is a finding284 with an owner. Drift is the metric of whether your governance is real or theatre.2857. **Periodic ruleset review** (quarterly at the perimeter, twice-yearly internally) producing286 a report with: **expired** rules, rules **without an owner**, **unused** ones (counter at zero for the whole287 period), **shadowed** ones (never reached because of an earlier rule), **duplicate** ones,288 **redundant** ones (a subset of another), **generalised** ones and **excessively permissive** ones. Each289 finding is closed with an action, not with a "reviewed". This report is direct evidence for290 `grc-compliance-standards`.2918. **Reachability verification before merge** in complex topologies (several firewalls in the292 path): model the change and check what it really opens and closes, including the rules of293 the other devices along the path. It is the only way to detect shadowing *between* boxes.294295## 5. Security296297### 5.1 Logging and observability of the policy298299- **`deny` events are logged, and they matter more than the `allow` ones.** An expected `allow` tells you300 nothing; a repeated denial is either an attack, or an uncommunicated change, or a missing301 rule. All three cases require action.302- **Format and fields**: a stable prefix identifiable per chain (`IN-DENY`, `FWD-DENY`,303 `OUT-DENY`), with source/destination IP, ports, protocol, interface and timestamp. The prefix is a304 contract: `detection-engineering-standards` builds rules on top of it and breaking it breaks their305 detections.306- **Volume and cost are a design problem, not an accident**: a `log` without `limit` in an Internet-facing307 `drop` chain is a self-denial of service and a SIEM bill. Always use308 `limit rate`, aggregate with `counter` whatever you only need to count, and decide what is sent309 to the SIEM and what stays in cheap storage. Logging everything at the same priority amounts to310 logging nothing.311- **Denied egress is the golden signal**: an internal host trying to reach a destination that is not312 permitted is, almost always, either misconfigured software or something that should not be there. That313 signal only exists if there is an egress policy.314- **Per-rule counters** as the source for the unused-rule review (§4.7) and for315 sizing. Without counters, "this rule is no longer needed" is an opinion.316317### 5.2 Containers: the classic of publishing a port and bypassing the firewall318319- **The failure**: when publishing a port (`-p 5432:5432`), Docker inserts its own NAT rules320 in `prerouting` and forwarding rules in `forward`. Inbound traffic towards the container **never321 traverses `input`**, which is where `ufw`'s policy and that of most host rulesets live.322 Result: "the port is blocked" and "the port is open" are true at the same time, and the323 service ends up exposed to the network while you believe otherwise. **Risk class: unintended324 exposure through a bypass of the filtering chain**, not a product vulnerability.325- **Mitigations, in order of preference**:326 1. **Do not publish what must not be public**: explicit bind to loopback (`127.0.0.1:5432:5432`)327 and a reverse proxy as the only surface. Most of these incidents are solved here.328 2. **Policy in `DOCKER-USER`**, which Docker evaluates **before** its own accept329 rules: it is the supported insertion point with the iptables backend, and it survives330 daemon restarts and container recreation.331 3. `internal` networks for whatever must not go out, and `expose` instead of `publish` when332 container-to-container communication is enough.333 4. Persist your rules with a systemd unit that runs **after** Docker; verify334 with a scan **from another machine**, never from the host itself.335- **What has changed (verified Aug 2026)**: **Docker Engine 28.0** hardened the default336 behaviour — unsolicited inbound traffic towards a container's internal IP is337 dropped unless the port has been explicitly published, closing the case of containers338 reachable from the LAN with `FORWARD` set to `ACCEPT`. **There is still an `input` bypass** for339 the ports you do publish: the fundamental pattern has **not** gone away.340 `DOCKER_INSECURE_NO_IPTABLES_RAW=1` disables `raw` table rules and **reopens** part of that341 hardening (including the protection of ports published on 127.0.0.1): vetoed in production.342- **Docker Engine 29** introduces an **experimental nftables backend**343 (`"firewall-backend": "nftables"`), which creates its own `ip docker-bridges` /344 `ip6 docker-bridges` tables, does **not** create a `DOCKER-USER` chain and does **not** support Swarm. Operational345 translation: if you enable it, your policy insertion point changes and your current rules stop346 applying. Lab only until it stops being experimental.347- **Distribution trap** (Debian 13 and equivalents): a container whose image ships348 `iptables-legacy` writing rules while the host uses `iptables-nft` leaves those rules in349 tables the kernel **does not consult**. They fail silently: the operator believes they have filtered.350- **Kubernetes**: `kube-proxy` and the CNI generate and regenerate their own chains on the node. **Do not351 edit their chains**; write your policy in your own tables/priorities that are evaluated earlier, and352 use `NetworkPolicy` for what is pod policy (`kubernetes-standards`). `kube-proxy` in353 nftables mode has been GA since 1.33 but **iptables is still the default**, and both modes354 coexist across a heterogeneous fleet: your node ruleset must tolerate both.355- Podman/netavark: the nftables driver is the supported path and the iptables one is being retired;356 verify which is active before writing rules around it.357358### 5.3 Clouds: security groups as the logical equivalent359360- Security Groups, NSG and NACL **are filtering policy** and **everything** in this document applies to them:361 default-deny, owner, justification, expiry, periodic review, IPv6 parity and deployment by362 code. The specific model and its limits, in `aws-standards`/`azure-standards`/`gcp-standards`.363- Differences that change the design and that must be kept in mind: SGs are usually **stateful and364 allow-only** (there is no "explicit deny", which eliminates shadowing but also the365 ability to make exceptions), NACLs are **stateless** (you have to open the return path and the ephemeral366 ports by hand, a common mistake), and there are **hard rule limits per group** that push you to367 group by role —which is, moreover, the correct design.368- **The SG does not replace the host firewall**: within the same group, traffic is usually369 permitted implicitly. And `0.0.0.0/0` in an SG is exactly the same finding as in370 a physical firewall.371372### 5.4 Management plane373374- Firewall administration arrives **only** from the OOB/bastion network, with MFA and named375 accounts; the firewall itself does not expose its management plane to user networks or to the Internet376 (OOB design in `networking-standards`, identity in377 `identity-access-management-standards`).378- **Golden anti-lockout rule**: the rule that permits your management access is the first one379 written, the last one touched and the one that never depends on a change in flight.380- Every change is **attributed to a person**: applied by CI from a signed PR. A381 change applied by hand on the device is, by definition, a change with no verifiable author.382- **Kernel and engine up to date**: netfilter itself is surface. **CVE-2026-23111** (UAF in383 `nf_tables`, local escalation to root via user namespaces, CVSS 7.8, published 2026-02-13) requires384 kernel ≥6.18.10/6.19 or the backports 5.15.200 / 6.1.163 / 6.6.124 / 6.12.70. Complementary385 mitigation while patching: restrict unprivileged `user namespaces` and access to386 `CAP_NET_ADMIN` (baseline detail in `linux-hardening-standards`).387388## 6. Performance and operability389390- **First-class metrics**: conntrack table occupancy against `nf_conntrack_max`391 (alert at 70-80%: it is a silent outage announced in advance), packet drop rate per chain,392 per-rule counters, network *softirq* CPU, and added latency along the path. Collection,393 thresholds and alerts, in `observability-standards`.394- **The cost of a badly written ruleset**: evaluation is linear per chain. Thousands of sequential395 rules where a **set** or a **map** would resolve in constant time is a performance396 *and* maintainability problem. nftables sets and maps are not syntactic sugar: they are the397 difference between reviewing 40 rules and reviewing 4,000.398- **Order by frequency, not by aesthetics**: `ct state established,related accept` first,399 always; the most common, earlier; the exceptional, later. And with `vmap` instead of long chains400 of comparisons when the criterion is a discrete value.401- **HA**: a firewall pair with state synchronisation (`conntrackd`/pfsync/VRRP) and **exercised**402 failover. Without synchronisation, every failover cuts all active sessions —sometimes that is403 acceptable, but it must be a decision, not a surprise.404- **Recovery**: the ruleset is restored from the repo onto a clean box in minutes, and that is405 tested (`bcdr-standards`). The backup of the device configuration is evidence, not406 a source.407- **Runbooks with an owner**: accidental lockout of management access, conntrack exhausted, loss of one408 node of the HA pair, an expired rule that cuts a service in production, legitimate traffic denied409 after a deployment, emergency opening (with its ≤72 h expiry already included in the410 template).411412## 7. Sustainability and prohibitions413414- **Cadence**: rule review quarterly at the perimeter and twice-yearly internally (§4.7); review415 of the engine version (nftables/firewalld) and of kernel/netfilter CVEs monthly; migration of everything416 still left in iptables syntax with a written exit date.417- **Continuous simplification**: each review must **reduce** the number of rules or justify why418 it grows. A ruleset that only grows is a ruleset nobody understands any more, and a ruleset nobody419 understands cannot be audited or changed safely.420- **Single ownership of the ruleset**: one manager per host. `ufw` + `firewalld` + your own nft421 rules + Docker fighting over the same ruleset produces unpredictable effective policy. Decide422 who is in charge and disable the rest explicitly.423424**FORBIDDEN**425- ❌ A base chain without `policy drop`; "default-allow and we will close things down as we go".426- ❌ A firewall without **egress** filtering; "it is an internal network, no need to filter outbound".427- ❌ A rule without an owner, without a business justification or without an expiry date.428- ❌ `any/any` (or `0.0.0.0/0`↔`::/0`) "temporary" without `expires` and without security approval.429- ❌ Changing the firewall remotely without a rescue session, an OOB console or timed rollback.430- ❌ Accepting a policy as good without **negative proof** that what is denied gets denied.431- ❌ Editing rules by hand on the device instead of through PR + CI; leaving drift uncorrected.432- ❌ The *running* configuration as the source of truth for the policy.433- ❌ Mixing `iptables` and `nftables` on the same host; writing new policy in iptables syntax;434 `iptables-legacy` on new systems.435- ❌ Several managers competing for the ruleset (`ufw` + `firewalld` + nft + Docker).436- ❌ **IPv4 policy without its IPv6 equivalent** (the most common and most exploited omission).437- ❌ Blocking ICMP indiscriminately (it kills PMTUD and diagnosis) or ICMPv6 (it breaks ND).438- ❌ `log` without `limit rate` in exposed drop chains.439- ❌ Not logging denials, or not forwarding them to detection engineering.440- ❌ Filtering against the public IP in rules that are evaluated **after** DNAT.441- ❌ Ignoring `ct state invalid`, or adding a broad `accept` to paper over an asymmetric route.442- ❌ Not monitoring conntrack occupancy.443- ❌ Publishing container ports without checking the real exposure **from another machine**.444- ❌ `DOCKER_INSECURE_NO_IPTABLES_RAW=1` in production; editing by hand the Docker,445 `kube-proxy` or CNI chains.446- ❌ Treating cloud Security Groups as something other than firewall policy.447- ❌ Relying on NAT/masquerade as a security control.448- ❌ A firewall management plane reachable from user networks or from the Internet.449- ❌ A kernel without the CVE-2026-23111 patch on multi-user hosts or hosts with untrusted containers.450- ❌ Hundreds of near-identical rules where a set or a map was the right answer.451452## 8. Mandatory web verification453454Before pinning any version, behaviour or limit, **look it up — do not recall it**.455Verified Aug 2026:456457- **nftables**: latest upstream **1.1.6 (2025-12-05)**; previous 1.1.5 (2025-08-27) and458 1.1.4 (2025-08-06). **firewalld 2.5.0 (2026-07-08)**, nftables backend.459- **iptables→nftables migration**: nftables is the default framework across all major460 distros; **`iptables-nft` still exists as a shim** in RHEL 9/10, Debian 13 (trixie) and461 Ubuntu — **its removal has not been confirmed in any of them**, only its deprecation.462 Verify on the project's **exact** distro. Confirmed trap: containers with463 `iptables-legacy` on an `iptables-nft` host write rules the kernel ignores.464- **ufw**: **it has no native nftables backend**; only `backend_iptables.py` on top of `iptables-nft`.465 The maintainer states ongoing maintenance but no priority for `backend_nft.py`.466- **Docker**: Engine **28.0** drops unsolicited inbound traffic to container IPs unless the467 port is published; the `input` bypass for published ports **persists**;468 `DOCKER_INSECURE_NO_IPTABLES_RAW=1` discouraged. Engine **29** adds469 `"firewall-backend": "nftables"` as **experimental** (tables `ip docker-bridges`/`ip6470 docker-bridges`, **no `DOCKER-USER`**, **no Swarm**).471- **Kubernetes**: `kube-proxy` nftables mode **GA in 1.33**, requires kernel ≥5.13, **is not the472 default** (iptables still is, with no announced change date); **IPVS deprecated in 1.35**.473- **CVE**: **CVE-2026-23111** — UAF in `nf_tables` (`nft_map_catchall_activate()`, inverted474 check), local LPE via user namespaces, **CVSS 7.8 (AV:L/AC:L/PR:L/UI:N/C:H/I:H/A:H)**,475 published 2026-02-13. Patched in **≥6.18.10 and 6.19**, backports **5.15.200, 6.1.163,476 6.6.124, 6.12.70**. Re-verify the current month's netfilter CVEs before pinning a minimum.477478**Declared gaps — do NOT fill from memory, verify before use**:4791. **nftables version packaged by distro**: only 1.1.4 on Fedora 44 is on record (inherited from480 `networking-standards`, not re-verified). The versions in RHEL 10, Debian 13 and Ubuntu LTS481 are **not verified**.4822. **Specific `ufw` version** current in 2026: **not obtained**. Check with `ufw --version`483 or on the distro's package page.4843. **Ruleset auditing tools**: `audit-springbok` exists (anomaly taxonomy:485 shadowing, redundancy, generalisation, correlation) and Batfish for pre-merge reachability486 verification, but **no native shadowing/redundancy analyser has been found487 for nftables**, and **the maintenance status and current version of both tools are not488 verified**. Do not recommend them as products without checking; the anomaly taxonomy in489 §4.7 is valid as a criterion.4904. **Detail of the Security Groups/NSG/NACL model** (rule limits, exact stateful semantics,491 IPv6 support): described at the level of criteria, **not verified against the current492 documentation of each provider**. Cross-check with `aws-standards`/`azure-standards`/`gcp-standards`.4935. **Status of the iptables driver in Podman/netavark** (deprecated or already removed?, in which version):494 only a maintainer's declared intent is on record. Verify before asserting it.4956. **Exact syntax and availability of `--check-config` in firewalld 2.5.0**: cited from memory,496 **not verified**.4977. **Status of conntrackd/pfsync and of state synchronisation options** on the specific498 perimeter platforms: not verified in this pass.499500If the web contradicts this document, **the web wins** — flag the discrepancy.