Zones, address sets (IPv4 and IPv6 mixed, dynamic sets with timeouts) and services, ordered rules in the early, input, forward and output chains, port forwards with hairpin NAT and IPv6 pinholes, source NAT, redirects, and switchable built-ins (loopback, conntrack, ICMPv6, antispoofing, MSS clamping, DHCPv6 client, flowtable). Every change is validated, checked with nft -c and applied in one transaction to Housecarl's own table, inet housecarl; changes made over the network roll back unless confirmed, and the confirmed ruleset is loaded at boot by housecarl-ruleset.service. An empty configuration applies nothing. Also: hit counters and the drop log (nflog), 'hc import' of an existing nftables ruleset and 'hc takeover', the hc CLI, the REST API and the web UI, users, scoped tokens (sets:write:NAME), the audit log, the web UI's certificate through Jarl, Debian packages, docs, unit tests with real nft 1.1.3, and an e2e test with real traffic (71 checks). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4.5 KiB
Importing an nftables ruleset
hc import turns the ruleset you have into Housecarl's configuration,
and hc takeover deletes the old table once Housecarl's replaces it.
Until then both apply.
1. Look first
On the firewall, as root:
sudo hc import --defines /etc/nftables.conf -n
hc runs nft -j list ruleset (and nft -a list ruleset, for the text
of rules it can't translate) and sends both to housecarld. -n (dry run)
shows what it would make, without saving anything: zones, sets,
services, rules, port forwards and NAT, then notes on every guess it
made, and the rules it kept raw.
--defines reads your ruleset file's define lines, so sets get your
names (define trusted_ips = { ... } becomes the set trusted_ips).
Without it, sets are named after what they hold. Interfaces named by a
define name their zone too (define wan_interface = "ppp0" makes the
zone wan); otherwise a zone is named after its interface.
From another machine, give the JSON as a file, and the text with
--text:
ssh fw 'sudo nft -j list ruleset' > ruleset.json
ssh fw 'sudo nft -a list ruleset' > ruleset.txt
hc import ruleset.json --text ruleset.txt --defines nftables.conf -n
With several tables, --table "inet main" picks one; by default every
table but Housecarl's is read. The web UI has the same under Server →
Import a ruleset.
What becomes what
- Interfaces become zones. Zones are marked external when the rules show it: MSS clamping on the way out, DHCPv6 client replies, ping accepted from everywhere else.
- Sets and defines of addresses become address sets; port lists become services.
- Jumps and gotos to your own chains are flattened: the jump's condition is added to each rule of the chain, in place.
- Rules keep their order. A rule that is a built-in (loopback, established, invalid, the ICMPv6 rules, MSS clamping, the DHCPv6 client rule, antispoofing) becomes that built-in only when it sits where the built-in goes, at the head of every chain it covers. Otherwise it stays a rule where it was, so the ruleset does what it did. Built-ins your ruleset doesn't have are turned off.
- DNAT rules become port forwards, TCP and UDP of the same port merged into one. The forward-chain accepts that let them through become part of the port forward, and a masquerade for inside sources to the same host becomes its hairpin. An IPv6 accept to the same port becomes the forward's IPv6 pinhole when there is only one candidate.
- Masquerade and SNAT become source NAT; redirect becomes a redirect.
- Policies become the chains' policies.
- Anything else becomes a raw rule with its original text, in place.
A raw rule naming a set of the old table needs the new set name:
Housecarl's are
NAME_v4andNAME_v6, and a note says so.
Chains on hooks Housecarl doesn't manage (ingress, a bridge table) are left out, with a note.
2. Import
sudo hc import --defines /etc/nftables.conf
sudo hc ruleset # what Housecarl now applies
sudo hc status # problems, and the other tables
Housecarl's table is now in force alongside the old one: a packet
must be accepted by both. Nothing should change for your traffic. If
Housecarl already has a configuration, --replace replaces it.
Imported over the network (not through the local socket), the import is on probation like any change: confirm it.
3. Take over
Once the rules look right (and their hits in the web UI agree), delete the old table:
sudo hc takeover "inet main"
From now on only Housecarl's rules apply. Over the network, this is on probation too: unless confirmed, the old table comes back.
4. The boot
Housecarl's ruleset is loaded at boot by housecarl-ruleset.service.
Debian's nftables.service loads /etc/nftables.conf, which usually
starts with flush ruleset: that would delete Housecarl's table, or
bring the old one back. Retire it:
sudo systemctl disable nftables.service
sudo mv /etc/nftables.conf /etc/nftables.conf.before-housecarl
housecarl-ruleset.service runs after nftables.service in any case,
so a leftover flush ruleset can't undo it. Docker, Incus and libvirt
keep adding their own tables as before.
Checking the result
hc rule list input(andforward) side by side withnft -a list table inet main: the same rules, in the same order, with built-ins above.- The notes: each says what to check.
hc countersafter a while: a rule with no hits that had hits before is worth a look.