jdx/mise · error
refusing firewall default incoming
Error message
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 What it means
Over SSH with a new default incoming policy, mise verifies that an incoming TCP allow rule covering your peer and server port appears BEFORE any rule whose action does not preserve access (drop/reject). If a blocking rule named earlier in the list would match your SSH traffic first, applying would cut your session, so it refuses for nftables and ufw, where first-match ordering applies.
Solutions
- Move the SSH allow rule above the blocking rule in the rules list.
- Narrow the blocking rule so it does not match your SSH peer/port (e.g. restrict its source/destination/interface).
- Make the blocking rule's action preserve access for established sessions, or accept the risk with allow_lockout = true.
Example fix
// before [[firewall.rules]] name = "deny-all" direction = "incoming" action = "deny" [[firewall.rules]] name = "allow-ssh" port = 22 protocol = "tcp" action = "allow" // after [[firewall.rules]] name = "allow-ssh" port = 22 protocol = "tcp" action = "allow" [[firewall.rules]] name = "deny-all" direction = "incoming" action = "deny"
Defensive patterns
Strategy: validation
Validate before calling
// ensure an allow rule for the SSH peer/port precedes any non-preserving rule
fn ssh_allow_is_first(rules: &[FirewallRule], peer: IpAddr, port: u16) -> bool {
for r in rules {
let covers = r.direction == Direction::Incoming
&& r.action.preserves_access()
&& r.matches(peer, port);
let blocks = r.direction == Direction::Incoming && !r.action.preserves_access();
if covers { return true; }
if blocks && r.could_match(peer, port) { return false; }
}
false
} Type guard
fn first_matching_rule_preserves(rules: &[FirewallRule], peer: IpAddr, port: u16) -> bool {
rules.iter()
.find(|r| r.direction == Direction::Incoming && r.could_match(peer, port))
.map(|r| r.action.preserves_access())
.unwrap_or(false)
} Try / catch
match result {
Err(e) if e.to_string().contains("precedes a proven allow") => {
eprintln!("move the SSH allow rule above the blocking rule");
}
other => other?,
} Prevention
- Adopt the convention: allow rules first, deny/catch-all last.
- Keep an SSH allow rule pinned at the top of the rules list in config.
- Preview with a dry run before applying default-incoming changes.
- Review rule ordering whenever editing [firewall] config.
When it happens
Trigger: `mise bootstrap firewall apply` (validate_safety) over an SSH session where the ordered rules include a rule before the covering allow whose action drops/rejects traffic and whose source/destination match the connection's peer/server. Triggered for FirewallsBackend::Nftables or Ufw when the covering allow has not already been proven earlier (covered == false).
Common situations: Inserting a catch-all deny at the top of the rules list and the SSH allow below it; reordering rules during a config cleanup; copying a hardened firewall profile that denies everything except LAN ranges while you SSH from a WAN address.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- refusing firewall default incoming
- refusing firewall default incoming
- refusing firewall default incoming
- refusing firewall default incoming
- SSH_CONNECTION must contain client address/port and server…
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/cceb3c44e00ab8a7.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/firewall.rs:511
for rule in rules.iter().filter(|rule| {
rule.state == FirewallRuleState::Present
&& rule.direction == FirewallDirection::Incoming
&& rule
.protocol
.is_none_or(|protocol| protocol == FirewallProtocol::Tcp)
&& rule
.port
.is_none_or(|port| port.contains(connection.server_port))
&& rule
.source
.is_none_or(|source| source.contains(&connection.peer))
&& rule
.destination
.is_none_or(|destination| destination.contains(&connection.server))
}) {
if !rule.action.preserves_access() {
match backend {
Some(FirewallBackend::Nftables | FirewallBackend::Ufw) if !covered => bail!(
"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",
self.default_incoming.ufw(),
rule.name,
connection.peer,
connection.server_port
),
Some(FirewallBackend::Firewalld) => bail!(
"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",
self.default_incoming.ufw(),
rule.name,
connection.peer,
connection.server_port
),
// An automatic backend is validated again after it is
// resolved.
_ => {}
}
continue;View on GitHub (pinned to 533346cc37)