# 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: ```sh 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 admins = { ... }` becomes the set `admins`). 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`: ```sh 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. * **Maps are expanded**, one element at a time. A DNAT map (`dnat ip to tcp dport map @forwards`, named or written in the rule) becomes one port forward per element; forwards from a named map are called after it (`forwards-443`). A verdict map (`tcp dport vmap { 22 : accept, 23 : drop }`) becomes one rule per element, in place, with `jump`s inlined as above. A lookup matches at most one element, so the rules do the same. A map keyed by several fields (`ip saddr . tcp dport`) can't be expanded: a rule using a named one is left out with a note, to be added by hand. * **Masquerade and SNAT** become source NAT; **redirect** becomes a redirect. * **Meters** (`update @m { ip saddr limit rate over 3/minute }`, `add @m { ip saddr ct count over 20 }`, quotas in sets) become the rule's per-source (or per-destination) limit, connection limit or quota; a mask in the key (`ip saddr and 255.255.255.0`) becomes its grouping. Byte rates and quotas are imported too; a quota imported resets only by hand until you give it a period. A rule with a quota or a byte rate that accepts stays a raw rule ([rules.md](rules.md#bandwidth-and-quotas)). * **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_v4` and `NAME_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 ```sh 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: ```sh sudo hc takeover "inet main" ``` From now on only Housecarl's rules apply. Until you do, the dashboard and `hc status` warn that the imported table still applies (`takeover_pending`); they also warn while `nftables.service` is enabled (`nftables_service`). 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: ```sh sudo systemctl disable nftables.service sudo mv /etc/nftables.conf /etc/nftables.conf.before-housecarl ``` Disable it, but **don't stop it** (no `systemctl stop nftables`, no `disable --now`): Debian's unit stops by running `nft flush ruleset`, which deletes every table, Housecarl's and Docker's included, at once. Disabled, it simply doesn't run at the next boot. `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` (and `forward`) side by side with `nft -a list table inet main`: the same rules, in the same order, with built-ins above. * The notes: each says what to check. * `hc counters` after a while: a rule with no hits that had hits before is worth a look.