Back to Mise

Linux host firewall

docs/bootstrap/firewall.md

2026.8.24.9 KB
Original Source

Linux host firewall

[bootstrap.linux.firewall] declaratively manages a Linux host firewall. It supports native nftables, firewalld policies, and UFW while keeping mise-owned rules separate from unrelated host rules.

toml
[bootstrap.linux.firewall]
backend = "auto"
state = "enabled"
default_incoming = "deny"
default_outgoing = "allow"

[[bootstrap.linux.firewall.rules]]
name = "https"
port = 443
protocol = "tcp"
action = "allow"

[[bootstrap.linux.firewall.rules]]
name = "ssh-admin"
port = 22
protocol = "tcp"
source = "203.0.113.10/32"
action = "allow"

Firewall convergence runs after packages, privileged files, and system services, but before Compose projects. This lets the configuration install and start its selected firewall backend before its policy is applied.

Backends

backend accepts:

  • "auto" (default): reuse the backend recorded by an earlier mise run, then prefer an already-active firewalld or UFW installation, then use nftables, firewalld, or UFW in that order when available.
  • "nftables": maintain an isolated inet mise_bootstrap table and a persistent mise-bootstrap-firewall.service. Runtime replacement is an atomic, syntax-checked nft transaction.
  • "firewalld": maintain the permanent mise-bootstrap-in and mise-bootstrap-out policies and reload firewalld only after its permanent configuration validates.
  • "ufw": maintain rules bearing mise:<name> comments in declared order, apply them before the default policy, and enable UFW after all rules are installed.

Explicitly selected backends fail closed when their command is unavailable. The selected backend and effective state are visible in status and plan.

Policy and rules

state accepts "enabled" (default), "disabled", or "absent". Removing the firewall section from config does nothing: deletion must be requested explicitly with state = "absent". disabled retains mise's saved rule model but removes the nftables/firewalld policy from the runtime; for UFW, it disables UFW globally. absent removes only mise-managed rules and metadata. Disabling and deleting are presented as destructive changes.

default_incoming and default_outgoing accept "allow", "deny", or "reject". Their defaults are incoming deny and outgoing allow.

Each [[bootstrap.linux.firewall.rules]] supports:

  • name (required): stable ASCII identifier used to reconcile the rule
  • state: "present" (default) or "absent"
  • direction: "incoming" (default) or "outgoing"
  • action: "allow" (default), "deny", or "reject"
  • protocol: "tcp", "udp", "sctp", or "dccp"
  • port: a number or inclusive string range such as "8000-8010"
  • source and destination: IPv4 or IPv6 CIDR networks
  • interface: an interface name (nftables and UFW only)

A port requires a protocol. One rule cannot mix IPv4 and IPv6 source and destination networks. UFW supports only TCP and UDP; firewalld policy rules do not safely support per-rule interface matching, so mise asks you to select nftables or UFW for those combinations rather than silently weakening a rule.

Ownership and deletion

By default, a later config preserves previously managed rules that it does not mention. Use a same-name rule with state = "absent" to remove one explicitly. This makes removing configuration non-destructive and permits separately layered configs to coexist.

Set exclusive = true only when the configuration owns the complete firewall. It drops undeclared mise rules. With UFW, exclusive mode performs ufw reset, which also removes unrelated UFW rules, and is therefore always confirmed as a destructive operation.

SSH lockout protection

When bootstrap runs over SSH and incoming policy is deny or reject, mise checks SSH_CONNECTION before making changes. At least one present incoming TCP allow rule must cover both the connected peer address and the server port. Otherwise apply fails before elevation. A deliberately out-of-band deployment can set allow_lockout = true as an explicit escape hatch.

Rules are installed in declared order before deny policies for UFW. nftables installs the complete ruleset atomically, and firewalld changes permanent policies before a single validated reload, so intermediate state cannot drop the active SSH connection. Non-exclusive UFW updates stage a uniquely tagged replacement ruleset before removing the previous mise rules, then replace the staging tags only after the stable rules are live. A later run safely cleans up staging rules left by an interrupted update.

sh
mise bootstrap firewall status
mise bootstrap firewall status --json
mise bootstrap firewall status --missing
mise bootstrap firewall apply --dry-run
mise bootstrap firewall apply --yes

Firewall management is Linux-only and uses the same constrained privileged helper protocol as bootstrap accounts, files, and services. Typed plans travel on stdin; config values are never interpolated into a root shell command.