{"record":{"id":"36a0fec3fb7c69f9","repo":"jdx/mise","slug":"firewall-rule-uses-interface-matching-which","errorCode":null,"errorMessage":"firewall rule '{}' uses interface matching, which firewalld policies cannot express safely; select backend = \\\"nftables\\\" or \\\"ufw\\\"","messagePattern":"firewall rule '(.+?)' uses interface matching, which firewalld policies cannot express safely; select backend = \\\\\"nftables\\\\\" or \\\\\"ufw\\\\\"","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/firewall.rs","lineNumber":1033,"sourceCode":"                && [FIREWALLD_INCOMING, FIREWALLD_OUTGOING]\n                    .iter()\n                    .all(|policy| {\n                        command_output(\n                            \"firewall-cmd\",\n                            &[\"--permanent\", &format!(\"--info-policy={policy}\")],\n                        )\n                        .is_ok_and(|output| output.status.success())\n                    })\n        }\n        FirewallBackend::Ufw => backend_active(backend),\n        FirewallBackend::Auto => false,\n    }\n}\n\nfn validate_backend_request(request: &FirewallRequest, backend: FirewallBackend) -> Result<()> {\n    for rule in &request.rules {\n        if backend == FirewallBackend::Firewalld && rule.interface.is_some() {\n            bail!(\n                \"firewall rule '{}' uses interface matching, which firewalld policies cannot express safely; select backend = \\\"nftables\\\" or \\\"ufw\\\"\",\n                rule.name\n            );\n        }\n        if backend == FirewallBackend::Ufw\n            && matches!(\n                rule.protocol,\n                Some(FirewallProtocol::Sctp | FirewallProtocol::Dccp)\n            )\n        {\n            bail!(\n                \"firewall rule '{}' uses protocol {}, which UFW does not support; select backend = \\\"nftables\\\" or \\\"firewalld\\\"\",\n                rule.name,\n                rule.protocol.expect(\"matched protocol\").as_str()\n            );\n        }\n    }\n    Ok(())","sourceCodeStart":1015,"sourceCodeEnd":1051,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/firewall.rs#L1015-L1051","documentation":"Interface matching (`interface = \"eth0\"` on a rule) cannot be expressed safely in firewalld policies — firewalld zone/policy semantics cannot guarantee the intended match — so `validate_backend_request` rejects any interface-using rule when the effective backend is firewalld. nftables and ufw both support interface matches, so the fix is to pin one of those backends or drop the `interface` key.","triggerScenarios":"`backend = \"firewalld\"` (or auto-detection resolving to firewalld) combined with any `[[bootstrap.linux.firewall.rules]]` entry that has an `interface` key — validated up front, before planning or dry-run output.","commonSituations":"RHEL-family hosts defaulting to firewalld while the config was authored on an Ubuntu/ufw or nftables machine; porting interface-scoped rules across distros; sharing dotfiles/bootstrap configs in mixed fleets.","solutions":["Pin `backend = \"nftables\"` or `backend = \"ufw\"` in `[bootstrap.linux.firewall]` (installing that backend if needed).","Remove the `interface` key from the flagged rule and constrain by `source`/`destination` CIDR instead.","If firewalld is mandatory, handle the interface constraint outside mise (firewalld zone/interface assignment) and keep the rule interface-free."],"exampleFix":"# before (mise.toml)\n[bootstrap.linux.firewall]\nbackend = \"firewalld\"\n\n[[bootstrap.linux.firewall.rules]]\nname = \"lan\"\ninterface = \"eth0\"\nport = 5353\nprotocol = \"udp\"\naction = \"allow\"\n\n# after\n[bootstrap.linux.firewall]\nbackend = \"nftables\"\n\n[[bootstrap.linux.firewall.rules]]\nname = \"lan\"\ninterface = \"eth0\"\nport = 5353\nprotocol = \"udp\"\naction = \"allow\"","handlingStrategy":"validation","validationCode":"# pre-flight: interface rules require nftables/ufw\npython3 - <<'PY'\nimport tomllib\nfw = tomllib.load(open('mise.toml','rb')).get('bootstrap',{}).get('linux',{}).get('firewall',{})\nif fw.get('backend') == 'firewalld' and any('interface' in r for r in fw.get('rules',[])):\n    raise SystemExit('firewalld cannot express interface matching; use backend = \"nftables\"/\"ufw\"')\nPY","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pin the backend explicitly in configs ported across distros.","Prefer source-CIDR matching over interface matching for portability.","Check rule features against the backend matrix before authoring."],"tags":["mise","firewall","firewalld","interface","backend","config","bootstrap"],"backgroundTag":"unsupported-feature-combination","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}