jdx/mise · error

firewall rule '{name}' sets port without protocol

Error message

firewall rule '{name}' sets port without protocol

What it means

In `[bootstrap.linux.firewall]`, a rule that sets `port` must also set `protocol` (tcp, udp, sctp, or dccp) because every firewall backend needs a protocol to render a port match. This error fires while building the FirewallRequest when `port` is present and `protocol` is absent. There is no default protocol — the config must be explicit.

Source

Thrown at src/system/firewall.rs:417

                .source
                .map(|source| source.parse::<IpNet>())
                .transpose()
                .wrap_err_with(|| format!("firewall rule '{name}' has an invalid source"))?;
            let destination = rule
                .destination
                .map(|destination| destination.parse::<IpNet>())
                .transpose()
                .wrap_err_with(|| format!("firewall rule '{name}' has an invalid destination"))?;
            if source.is_some_and(|source| {
                destination.is_some_and(|destination| {
                    source.addr().is_ipv4() != destination.addr().is_ipv4()
                })
            }) {
                bail!("firewall rule '{name}' mixes IPv4 and IPv6 source/destination networks");
            }
            let port = rule.port.map(FirewallPort::from_toml).transpose()?;
            if port.is_some() && rule.protocol.is_none() {
                bail!("firewall rule '{name}' sets port without protocol");
            }
            rules.push(FirewallRule {
                name,
                state: rule.state,
                direction: rule.direction,
                action: rule.action,
                port,
                protocol: rule.protocol,
                source,
                destination,
                interface,
            });
        }
        let ssh_connection = std::env::var("SSH_CONNECTION")
            .ok()
            .map(|value| parse_ssh_connection(&value))
            .transpose()?;
        let request = Self {

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Add `protocol = "tcp"` (or "udp") to the rule.
  2. If both protocols are needed, create two rules with the same constraints but different protocols.
  3. Note sctp/dccp are only usable with the nftables/firewalld backends — ufw rejects them (see the separate validation error).

Example fix

# before
[[bootstrap.linux.firewall.rules]]
name = "ssh"
port = 22

# after
[[bootstrap.linux.firewall.rules]]
name = "ssh"
port = 22
protocol = "tcp"
Defensive patterns

Strategy: validation

Validate before calling

# pre-flight: port requires protocol
python3 - <<'PY'
import tomllib
fw = tomllib.load(open('mise.toml','rb')).get('bootstrap',{}).get('linux',{}).get('firewall',{})
for r in fw.get('rules',[]):
    if 'port' in r and 'protocol' not in r:
        raise SystemExit(f"rule {r['name']}: sets port without protocol")
PY

Prevention

When it happens

Trigger: A rule containing `port = 22` (or `port = "22-30"`) with no `protocol` key; the check is `port.is_some() && rule.protocol.is_none()` after the port is successfully parsed.

Common situations: Porting rules from ufw syntax (`ufw allow 22` implicitly means tcp+udp) where protocol looks redundant; trimming config to minimal keys; assuming a tcp default like SSH-oriented examples often show.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


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