{"record":{"id":"34b5e5ff07039f33","repo":"jdx/mise","slug":"refusing-firewall-default-incoming-over-ssh-bl-34b5e5","errorCode":null,"errorMessage":"refusing firewall default incoming {} over SSH: blocking rule '{}' also covers peer {} on server port {}, and firewalld cannot guarantee the allow wins; narrow the rule or set allow_lockout = true","messagePattern":"refusing firewall default incoming (.+?) over SSH: blocking rule '(.+?)' also covers peer (.+?) on server port (.+?), and firewalld cannot guarantee the allow wins; narrow the rule or set allow_lockout = true","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/system/firewall.rs","lineNumber":509,"sourceCode":"                    .port\n                    .is_none_or(|port| port.contains(connection.server_port))\n                && rule\n                    .source\n                    .is_none_or(|source| source.contains(&connection.peer))\n                && rule\n                    .destination\n                    .is_none_or(|destination| destination.contains(&connection.server))\n        }) {\n            if rule.action != FirewallAction::Allow {\n                match backend {\n                    Some(FirewallBackend::Nftables | FirewallBackend::Ufw) if !covered => bail!(\n                        \"refusing firewall default incoming {} over SSH: blocking rule '{}' precedes a proven allow for peer {} on server port {}; reorder or narrow the rule, or set allow_lockout = true\",\n                        self.default_incoming.ufw(),\n                        rule.name,\n                        connection.peer,\n                        connection.server_port\n                    ),\n                    Some(FirewallBackend::Firewalld) => bail!(\n                        \"refusing firewall default incoming {} over SSH: blocking rule '{}' also covers peer {} on server port {}, and firewalld cannot guarantee the allow wins; narrow the rule or set allow_lockout = true\",\n                        self.default_incoming.ufw(),\n                        rule.name,\n                        connection.peer,\n                        connection.server_port\n                    ),\n                    // An automatic backend is validated again after it is\n                    // resolved.\n                    _ => {}\n                }\n                continue;\n            }\n            // SSH_CONNECTION does not identify the ingress interface. An\n            // interface-constrained allow cannot prove that it preserves this\n            // session, so keep looking for an unrestricted covering allow.\n            if rule.interface.is_none() {\n                covered = true;\n                if matches!(","sourceCodeStart":491,"sourceCodeEnd":527,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/firewall.rs#L491-L527","documentation":"The firewalld variant of the lockout guard: firewalld policies give no strict first-match ordering mise can rely on, so a blocking rule that also covers the current SSH peer and server port is refused regardless of declaration order — an allow elsewhere cannot be proven to win. The fix is to make the blocking rule stop matching the session, or to acknowledge the risk explicitly.","triggerScenarios":"Backend firewalld (pinned or auto-resolved), deny-ish `default_incoming`, and any non-allow rule whose source/port/destination match the `SSH_CONNECTION` peer and server port — even if an allow rule for the same session exists, firewalld cannot guarantee it takes precedence.","commonSituations":"RHEL/CentOS-family hosts where firewalld is the default; migrating iptables rule sets into policy objects; broad denies coexisting with a specific SSH allow on hosts that previously used nftables ordering semantics.","solutions":["Narrow the blocking rule until it cannot match the session: different source CIDR excluding your peer, a port range excluding the SSH port, or a different interface scope.","Pin `backend = \"nftables\"` or `\"ufw\"` (installing that tool if needed) where declaration order is honored, and put the allow first.","With out-of-band console access, set `allow_lockout = true` under `[bootstrap.linux.firewall]` to accept the ambiguity."],"exampleFix":"# before — firewalld cannot prove the allow wins\nbackend = \"firewalld\"\n[[bootstrap.linux.firewall.rules]]\nname = \"deny-guest\"\nsource = \"0.0.0.0/0\"\nport = \"1-65535\"\nprotocol = \"tcp\"\naction = \"deny\"\n\n[[bootstrap.linux.firewall.rules]]\nname = \"ssh-ops\"\nsource = \"203.0.113.5/32\"\nport = 22\nprotocol = \"tcp\"\naction = \"allow\"\n\n# after — exclude the management peer from the deny\n[[bootstrap.linux.firewall.rules]]\nname = \"deny-guest\"\nsource = \"0.0.0.0/1\"        # plus a second rule for 128.0.0.0/1, both excluding the peer\nport = \"1-21\"\nprotocol = \"tcp\"\naction = \"deny\"\n# or simpler: pin backend = \"nftables\" and order the allow first","handlingStrategy":"try-catch","validationCode":"# pre-flight: on firewalld, no deny rule may cover the SSH peer/port at all\n# (order cannot save you; narrow or exclude the peer before apply)","typeGuard":null,"tryCatchPattern":"if ! mise bootstrap firewall apply 2>fw.err; then\n  if grep -q \"firewalld cannot guarantee the allow wins\" fw.err; then\n    echo \"narrow the deny so it stops covering the SSH peer/port, or switch backend\"; exit 1\n  fi\n  cat fw.err; exit 1\nfi","preventionTips":["On firewalld hosts, never combine broad denies with a specific SSH allow — split or narrow them.","Pin backend explicitly when porting configs across distros so ordering semantics match your rules.","Keep management traffic on a dedicated port/CIDR that no deny rule touches."],"tags":["mise","firewall","ssh","firewalld","lockout","rule-ordering","bootstrap"],"backgroundTag":"firewall-lockout-protection","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}