jdx/mise · error

firewall port '{range}' must be a number or inclusive range

Error message

firewall port '{range}' must be a number or inclusive range

What it means

Raised while parsing a `[bootstrap.linux.firewall]` rule's `port` key. The key is an untagged TOML value: either a bare integer (single port) or a string containing an inclusive range with a `-` or `:` separator ("8000-9000", "8000:9000"). This error means a string was supplied but contained no range separator, so it cannot be split into start/end — almost always a single port that was quoted.

Source

Thrown at src/system/firewall.rs:190

#[serde(untagged)]
pub enum FirewallPortToml {
    Single(u16),
    Range(String),
}

#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct FirewallPort {
    start: u16,
    end: u16,
}

impl FirewallPort {
    fn from_toml(value: FirewallPortToml) -> Result<Self> {
        let (start, end) = match value {
            FirewallPortToml::Single(port) => (port, port),
            FirewallPortToml::Range(range) => {
                let Some((start, end)) = range.split_once(['-', ':']) else {
                    bail!("firewall port '{range}' must be a number or inclusive range")
                };
                (start.parse()?, end.parse()?)
            }
        };
        if start == 0 || end == 0 || start > end {
            bail!("firewall port range {start}-{end} is invalid");
        }
        Ok(Self { start, end })
    }

    fn contains(self, port: u16) -> bool {
        self.start <= port && port <= self.end
    }

    fn render(self, separator: char) -> String {
        if self.start == self.end {
            self.start.to_string()
        } else {

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Use a bare integer for a single port: `port = 443` (remember `protocol = "tcp"` is required when port is set).
  2. Use an inclusive range string: `port = "8000-9000"` or `port = "8000:9000"`.
  3. List each port in its own `[[bootstrap.linux.firewall.rules]]` entry — comma lists are not supported.
  4. Validate config cheaply with `mise bootstrap firewall status` (it parses the config without applying) before `apply`.

Example fix

# before (mise.toml)
[[bootstrap.linux.firewall.rules]]
name = "https"
port = "443"

# after
[[bootstrap.linux.firewall.rules]]
name = "https"
port = 443
protocol = "tcp"
# or a range: port = "8000-9000", protocol = "tcp"
Defensive patterns

Strategy: validation

Validate before calling

# pre-flight: quoted single ports and separator-less strings
python3 - <<'PY'
import tomllib
fw = tomllib.load(open('mise.toml','rb')).get('bootstrap',{}).get('linux',{}).get('firewall',{})
for r in fw.get('rules',[]):
    p = r.get('port')
    if isinstance(p, str) and not any(c in p for c in '-:'):
        raise SystemExit(f"rule {r['name']}: port string '{p}' has no range separator; use an integer")
PY

Prevention

When it happens

Trigger: `port = "443"` in a rule: the untagged enum first tries `Single(u16)` and fails because the value is a TOML string, then `Range("443")` fails `split_once(['-', ':'])`. Also triggered by free-form strings like "https", "443," or "443 8443" (comma/space lists are unsupported).

Common situations: Quoting numbers out of TOML habit; copying port strings from docker/ufw documentation that uses "443" or "443,8443" forms; YAML-to-TOML migrations that stringified values.

Related errors


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