jdx/mise · error

firewall rule '{}' uses interface matching, which firewalld

Error message

firewall rule '{}' uses interface matching, which firewalld policies cannot express safely; select backend = \"nftables\" or \"ufw\"

What it means

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.

Source

Thrown at src/system/firewall.rs:1033

                && [FIREWALLD_INCOMING, FIREWALLD_OUTGOING]
                    .iter()
                    .all(|policy| {
                        command_output(
                            "firewall-cmd",
                            &["--permanent", &format!("--info-policy={policy}")],
                        )
                        .is_ok_and(|output| output.status.success())
                    })
        }
        FirewallBackend::Ufw => backend_active(backend),
        FirewallBackend::Auto => false,
    }
}

fn validate_backend_request(request: &FirewallRequest, backend: FirewallBackend) -> Result<()> {
    for rule in &request.rules {
        if backend == FirewallBackend::Firewalld && rule.interface.is_some() {
            bail!(
                "firewall rule '{}' uses interface matching, which firewalld policies cannot express safely; select backend = \"nftables\" or \"ufw\"",
                rule.name
            );
        }
        if backend == FirewallBackend::Ufw
            && matches!(
                rule.protocol,
                Some(FirewallProtocol::Sctp | FirewallProtocol::Dccp)
            )
        {
            bail!(
                "firewall rule '{}' uses protocol {}, which UFW does not support; select backend = \"nftables\" or \"firewalld\"",
                rule.name,
                rule.protocol.expect("matched protocol").as_str()
            );
        }
    }
    Ok(())

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Pin `backend = "nftables"` or `backend = "ufw"` in `[bootstrap.linux.firewall]` (installing that backend if needed).
  2. Remove the `interface` key from the flagged rule and constrain by `source`/`destination` CIDR instead.
  3. If firewalld is mandatory, handle the interface constraint outside mise (firewalld zone/interface assignment) and keep the rule interface-free.

Example fix

# before (mise.toml)
[bootstrap.linux.firewall]
backend = "firewalld"

[[bootstrap.linux.firewall.rules]]
name = "lan"
interface = "eth0"
port = 5353
protocol = "udp"
action = "allow"

# after
[bootstrap.linux.firewall]
backend = "nftables"

[[bootstrap.linux.firewall.rules]]
name = "lan"
interface = "eth0"
port = 5353
protocol = "udp"
action = "allow"
Defensive patterns

Strategy: validation

Validate before calling

# pre-flight: interface rules require nftables/ufw
python3 - <<'PY'
import tomllib
fw = tomllib.load(open('mise.toml','rb')).get('bootstrap',{}).get('linux',{}).get('firewall',{})
if fw.get('backend') == 'firewalld' and any('interface' in r for r in fw.get('rules',[])):
    raise SystemExit('firewalld cannot express interface matching; use backend = "nftables"/"ufw"')
PY

Prevention

When it happens

Trigger: `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.

Common situations: 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.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/36a0fec3fb7c69f9. Report an issue: GitHub.