Rocky 10: why direct rules should become policies
Test scope
Section titled “Test scope”Rocky Linux 10.2 VM with firewalld 2.4.0. In one session: read the deprecation paragraph, add a direct rule, then allow the same service with a policy and compare where each lands in nft.
Screenshot: public/evidence/firewalld/rocky-direct-vs-policy.png.
The official stance first
Section titled “The official stance first”$ man firewalld.directDEPRECATED The direct interface has been deprecated. It will be removed in a future release. It is superseded by policies, see firewalld.policies(5).“Will be removed” plus “superseded by policies” is enough to stop writing --direct for new work.
What a direct rule looks like in nft
Section titled “What a direct rule looks like in nft”$ firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 \ -p tcp --dport 9999 -j ACCEPTsuccess$ firewall-cmd --reload$ nft list ruleset | grep -B4 "dport 9999" | grep chain chain INPUT {Direct rules land in a bare INPUT chain with no zone association. Three consequences:
- zone defaults, logging and rich-rule conditions do not apply;
- ordering is manual via the priority number, easy to shadow other rules;
firewall-cmd --list-allnever shows them — only--direct --get-all-rulesdoes.
The equivalent policy
Section titled “The equivalent policy”# firewall-cmd --permanent --new-policy labpolicy# firewall-cmd --permanent --policy labpolicy --add-ingress-zone public# firewall-cmd --permanent --policy labpolicy --add-egress-zone HOST# firewall-cmd --permanent --policy labpolicy --add-port 9997/tcp# firewall-cmd --permanent --policy labpolicy --set-target ACCEPT# firewall-cmd --reload$ firewall-cmd --info-policy=labpolicy target: ACCEPT ingress-zones: public egress-zones: HOST ports: 9997/tcp$ nft list ruleset | grep -B3 "dport 9997" | grep chain chain filter_IN_policy_labpolicy_allow {A policy gets its own chain, so it does not overwrite zone rules, and the intent is recorded in the configuration itself: who enters, where to, what is allowed.

Which one to pick
Section titled “Which one to pick”- One port on a single host:
firewall-cmd --add-portin a zone; - Source restrictions, logging, rate limiting: rich rules;
- Forwarding, containers, host-wide intent: policies;
--direct: legacy compatibility only.
Three migration gotchas
Section titled “Three migration gotchas”- ingress zone cannot be
any— measuredINVALID_ZONE: 'any' not among existing zones; use a real zone such aspublic; --set-target ACCEPTis broad — it allows the whole zone pair and can shadow the zone’s default deny;- runtime and permanent must match — runtime-only changes vanish on reload.
Common traps
Section titled “Common traps”- A rule missing from
--list-allmay live in direct or a policy; check--direct --get-all-rulesand--list-policies; - Removing a direct rule requires an exact match and fails silently when it does not match;
- A policy can exist but be inactive —
--info-policyshows the state, do not judge by nft chains alone.
Practice: migrate direct to policy
Section titled “Practice: migrate direct to policy”sudo firewall-cmd --list-policiessudo firewall-cmd --permanent --new-policy lab9997sudo firewall-cmd --permanent --policy lab9997 --add-ingress-zone publicsudo firewall-cmd --permanent --policy lab9997 --add-egress-zone HOSTsudo firewall-cmd --permanent --policy lab9997 --add-port 9997/tcpsudo firewall-cmd --permanent --policy lab9997 --set-target ACCEPTsudo firewall-cmd --reloadsudo firewall-cmd --info-policy=lab9997Checks:
- The policy is active with the expected ingress/egress;
nft list ruleset | grep 9997shows the allow;- Removing the old direct rule changes nothing;
- Delete the policy afterwards.
常见问题
Do policies conflict with zones?
No. They are separate nft chains evaluated in hook order; conflicts usually come from a target that is too permissive.
Do I still need direct priorities?
No. Policies express intent through zone pairs and targets instead of manual priority numbers.
What about an existing direct.xml?
Export with --direct --get-all-rules, map each rule to a policy or rich rule, verify, then empty direct.xml.
Next steps
Section titled “Next steps”- nftables backend: /en/firewall-backend-nftables/
- Rich rules: /en/firewalld-rich-rules/
- Zones: /en/firewalld-zones/