jdx/mise · error
firewall rule '{name}' mixes IPv4 and IPv6 source/destinatio
Error message
firewall rule '{name}' mixes IPv4 and IPv6 source/destination networks What it means
A firewall rule may filter on both `source` and `destination` CIDR networks, but both must belong to the same IP family. When one side is IPv4 and the other IPv6 (source.addr().is_ipv4() differs), the match cannot be expressed as a single backend rule and config parsing aborts with this error. The fields themselves parsed fine — only the family combination is rejected.
Source
Thrown at src/system/firewall.rs:413
.interface
.map(|interface| validate_interface(interface.trim()))
.transpose()?;
let source = rule
.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")View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Make both sides the same family: change the destination to an IPv4 net, or the source to an IPv6 net.
- Split into two rules — one IPv4-only, one IPv6-only — each with matching families.
- Omit `source` or `destination` when you only need to match one side (a missing side matches any address).
Example fix
# before [[bootstrap.linux.firewall.rules]] name = "lan-to-services" source = "192.168.1.0/24" destination = "fd00::/64" port = 8443 protocol = "tcp" # after [[bootstrap.linux.firewall.rules]] name = "lan-to-services-v4" source = "192.168.1.0/24" destination = "10.0.0.0/8" port = 8443 protocol = "tcp" [[bootstrap.linux.firewall.rules]] name = "lan-to-services-v6" source = "fd00:ab::/48" destination = "fd00::/64" port = 8443 protocol = "tcp"
Defensive patterns
Strategy: validation
Validate before calling
# pre-flight: source and destination must share an IP family
python3 - <<'PY'
import tomllib, ipaddress
fw = tomllib.load(open('mise.toml','rb')).get('bootstrap',{}).get('linux',{}).get('firewall',{})
for r in fw.get('rules',[]):
s, d = r.get('source'), r.get('destination')
if s and d and ipaddress.ip_network(s).version != ipaddress.ip_network(d).version:
raise SystemExit(f"rule {r['name']}: mixed families {s} vs {d}")
PY Prevention
- On dual-stack hosts, author one rule per family instead of one mixed rule.
- When adapting example rules, change both sides or drop one.
- Let a config linter compare ip_network().version on each pair.
When it happens
Trigger: A rule with `source = "192.168.1.0/24"` and `destination = "fd00::/64"` (or the reverse) — both parse as IpNet, then the `is_ipv4()` comparison fails. Common on dual-stack rules copied from nftables examples.
Common situations: Dual-stack hosts where the LAN is IPv4 but services sit on IPv6 (or vice versa); editing only one address when adapting an example rule; leftover ULA default (fd00::/8) combined with private IPv4 ranges.
Related errors
- firewall port '{range}' must be a number or inclusive range
- firewall port range {start}-{end} is invalid
- firewall rule '{}' is declared more than once
- firewall rule '{name}' is declared more than once
- firewall rule '{name}' sets port without protocol
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/c4149a782a828bad.
Report an issue: GitHub.