Documentation

Last updated: 2026-08-11

How to host a root domain with primary at home and secondary on a VPS

This walkthrough sets up two Everlock instances to serve one root domain:

  • Everlock A runs at home behind your Fritzbox on a dynamic public IP. You edit the zone here.
  • Everlock B runs on a small VPS with a static public IPv4. It serves every real query from the internet.

The trick: only the VPS's IP is ever registered at the registrar. The home IP stays private. When the home line reconnects, the secondary learns the new endpoint from the next NOTIFY's source IP and re-fetches the zone — no glue updates at the registrar, ever.

This is the recommended residential-friendly setup. If you have a static IP everywhere, the single-instance static guide is simpler. If you want to host only from home and accept the operational friction, see the NAT + GoDaddy guide.


What you need

  • A registered domain (jens.dev in this walkthrough).
  • A box at home that can run Everlock and accept inbound TCP 53 from the VPS. Inbound UDP 53 is not required — the secondary doesn't query for normal records, only does AXFR over TCP.
  • A small VPS with a public static IPv4 (€3/month tier is plenty). Both UDP and TCP 53 open inbound.
  • The home line needs a port-forward for inbound TCP 53 only.

1. Open the right ports

On the VPS (Everlock B)

sudo ufw allow 53/udp
sudo ufw allow 53/tcp

Plus the same in the cloud firewall, if your provider has one. UDP/53 is for normal resolver queries; TCP/53 is for AXFR + large responses + the cloud's resolver fallback.

On the home Fritzbox (Everlock A)

Forward only TCP port 53 inbound to the Everlock host:

  1. Internet → Permit Access → Port Sharing.
  2. Protocol TCP, external 53 → internal 53 on the Everlock host's LAN IP.
  3. Save.

Don't forward UDP 53. The home node only needs to send NOTIFY (outbound UDP, no port-forward) and answer AXFR (inbound TCP). The world doesn't see the home box at all.


2. Start both Everlock instances

Home (Everlock A)

everlock serve \
  --frontend-ssh \
  --backend-admin-ssh \
  --frontend-dns \
  --frontend-dns-listen-udp 0.0.0.0:53,[::]:53 \
  --frontend-dns-listen-tcp 0.0.0.0:53,[::]:53 \
  --backend-dns-dns \
  --admin-user admin --admin-password change-me

VPS (Everlock B)

Same command, same flags. Different data directory of course.

Both bind port 53 — needs root or CAP_NET_BIND_SERVICE:

sudo setcap CAP_NET_BIND_SERVICE=+eip ./everlock

3. Generate a shared TSIG key

The two Everlocks authenticate every NOTIFY and AXFR with a shared HMAC-SHA256 secret. Generate it once on either side; the key value goes into both zone TOMLs verbatim.

SSH into either Everlock and run:

ssh -p 2222 admin@<host>
/dns peer keygen

You get something like:

Generated TSIG key (hmac-sha256, 32 bytes):

  rJqM2X7nP9eF4kVZ1aL8sQwY3uH6tD0bC5gN+iWvxk0=

Save that string — you'll paste it into both zone files in the next steps. Anyone with that key can forge NOTIFY/AXFR for the zone, so don't paste it into chat logs.


4. Configure the home Everlock (primary)

On the home box, create the zone first:

/users grant admin ssh/dns/* owner
/dns zones create jens.dev

Then declare the VPS as a downstream peer. Newly-created zones are already mode = "primary", so this is a single command — paste the key from the previous step as secret=:

/dns peer create jens.dev name=vps address=203.0.113.42 \
    secret=rJqM2X7nP9eF4kVZ1aL8sQwY3uH6tD0bC5gN+iWvxk0=

address is the VPS's public IPv4; algorithm defaults to hmac-sha256. The command writes the [[peer]] block into data/everlock-dns/jens.dev.toml and reloads for you — you never touch the file by hand. (You can still edit the TOML directly if you prefer; both paths end in the same place.) Managing peers requires the owner role on the zone, which /dns zones create granted you above.

If one secondary answers from several nameserver IPs under a single shared key — common with hosted secondary providers — pass them all, comma-separated, and every one receives the NOTIFY:

/dns peer create jens.dev name=vps \
    address=203.0.113.42,203.0.113.43,203.0.113.44 \
    secret=rJqM2X7nP9eF4kVZ1aL8sQwY3uH6tD0bC5gN+iWvxk0=

Also set the home Everlock's address mode in data/everlock-system/config/dns.toml so it knows what to announce as its own ns1/ns2 glue (this is internal — only the secondary will ever see these records, not the registrar):

enabled = true
listen_udp = "0.0.0.0:53,[::]:53"
listen_tcp = "0.0.0.0:53,[::]:53"
address_mode = "discover"

Reload:

/dns reload
/dns peer list jens.dev

/dns peer list jens.dev should show the vps peer, its hmac-sha256 algorithm, and its NOTIFY address(es).


5. Configure the VPS Everlock (secondary)

On the VPS, create the zone in secondary mode and point it at the home node as its upstream master. Two commands:

/users grant admin ssh/dns/* owner
/dns zones create jens.dev mode=secondary
/dns peer create jens.dev name=vps address=203.0.113.99 \
    secret=rJqM2X7nP9eF4kVZ1aL8sQwY3uH6tD0bC5gN+iWvxk0=
  • mode=secondary makes this a mirror: its records are owned by the primary and arrive via AXFR, so record edits are rejected here.
  • name=vps must match the [[peer]] name on the home side.
  • address=203.0.113.99 is the home line's current public IPv4. It's a one-time bootstrap — the secondary learns the home node's actual current IP from the source of the first signed NOTIFY that arrives, and keeps it up to date in memory from then on. Any reachable value works to start.

On a secondary, peer create sets the single [primary_peer] block; it errors if one already exists — use /dns peer set to replace it. (Both commands just write data/everlock-dns/jens.dev.toml and reload; you can still edit that file by hand if you prefer.)

Then in data/everlock-system/config/dns.toml:

enabled = true
listen_udp = "0.0.0.0:53,[::]:53"
listen_tcp = "0.0.0.0:53,[::]:53"
address_mode = "static"
default_ipv4 = "203.0.113.42"
default_ipv6 = "2001:db8::42"   # if you have v6

Reload:

/dns reload
/dns records list jens.dev

Right after a /dns reload, before any AXFR has happened, the secondary serves SERVFAIL for the zone (it has the zone config but no records yet). The next step triggers the AXFR.


6. Trigger the first replication

The /dns peer create in step 4 already fired the first NOTIFY, so the VPS may have transferred the zone the moment you configured it. To trigger replication again on demand, change something — the SOA serial only advances when the zone's effective content actually changes, so a bare /dns reload with no edits will not NOTIFY (it reuses the existing serial). Make a real change, e.g.:

/dns records create jens.dev name=_test type=TXT value=hello ttl=300

Watch the logs on both sides:

  • Home: NOTIFY sent (zone=jens.dev, peer=vps, serial=…)
  • VPS: DNS NOTIFY for jens.dev from <home-ip>:… followed by master endpoint for zone jens.dev set to <home-ip>:53 and then AXFR succeeded for jens.dev (serial …).

Verify the VPS now serves the zone:

dig @203.0.113.42 jens.dev SOA
dig @203.0.113.42 jens.dev NS

You should see authoritative answers (AA flag).

On the VPS:

/dns records list jens.dev

Every row should be tagged replicated.


7. Register the delegation at the registrar

This is the same step as in the single-static-IP guide, but with one crucial difference: the glue points only at the VPS's IPs, never at your home IP.

In the registrar panel for jens.dev:

a) Set the nameservers

Switch the delegation to:

  • ns1.jens.dev
  • ns2.jens.dev

b) Register glue records (registry-level A/AAAA)

HostIPv4IPv6 (optional)
ns1203.0.113.422001:db8::42
ns2203.0.113.422001:db8::42

Both NS names resolve to the VPS. (If you eventually run a second VPS for redundancy, point ns2 glue at its IP. Phase 1 doesn't require this.)

Save. The home IP is never published anywhere public.


8. Verify the public path

dig @8.8.8.8 jens.dev SOA
dig @1.1.1.1 jens.dev NS
dig +trace jens.dev

+trace should walk from the root → .devns1.jens.dev (your VPS) and return authoritative answers from there.


9. Add records — they auto-replicate

From now on, edit records on the home Everlock:

/users grant admin ssh/dns/jens.dev writer
/dns records create jens.dev name=@ type=A value=203.0.113.42 ttl=300
/dns records create jens.dev name=www type=CNAME value=jens.dev ttl=300

Each /dns records create reloads the zone, bumps SOA, and NOTIFYs the VPS. The VPS pulls AXFR and starts answering with the new records within seconds.

Verify against the VPS:

dig @203.0.113.42 www.jens.dev CNAME

You'll never need to edit anything on the VPS again — it's a pure mirror.


10. What happens when the home IP changes

This is what the whole topology buys you. Walk through the chain on the next forced reconnect:

  1. Home line reconnects with a new public IPv4.
  2. Within minutes, Everlock A's discover loop notices and bumps its internal records' SOA.
  3. NOTIFY fires from the new source IP, signed with the shared TSIG key.
  4. The VPS verifies the TSIG, stores the new source IP as the master endpoint, and pulls AXFR back to it.
  5. The VPS serves the latest zone unchanged. The internet sees no blip.

You don't touch the registrar. You don't touch the VPS. You don't touch glue records. The home IP is private knowledge between you and the VPS.

If the home Everlock is offline (you're rebooting, the DSL line is down), the VPS keeps serving the last zone it AXFR'd. Once the home node comes back, the next NOTIFY re-syncs everything.


Troubleshooting

"AXFR failed: TSIG verification failed"

The secret strings don't match. Re-copy the key from /dns peer keygen into both zone TOMLs exactly — base64 padding included.

"NOTIFY failed after retries"

The home → VPS UDP path isn't working. Check that the VPS firewall allows inbound UDP 53 from anywhere. NOTIFYs come from arbitrary source ports.

"no master endpoint known for zone jens.dev"

The secondary hasn't received a NOTIFY since startup. Either make a record change on the home side to emit one (a bare /dns reload won't — the serial only moves on a real change), or wait for the hourly heartbeat to kick the AXFR client (which will fall back to the bootstrap address in [primary_peer]).

"AXFR rejected: TSIG verification failed for vps"

The TSIG key in the home zone TOML ([[peer]] secret) and the VPS zone TOML ([primary_peer] secret) differ. They must be identical.

/dns reload on the VPS shows the zone but dig returns SERVFAIL

The secondary has the zone config but no AXFR has completed. Make a record change on the home side to emit a NOTIFY; if nothing happens, check the home Everlock's log for the NOTIFY attempt, and check that the home firewall lets TCP 53 inbound from the VPS for the AXFR.


Phase 1 limitations to know about

  • Single-message AXFR. Zones must fit in ~64KB on the wire (~500 typical records). Plenty for a personal domain.
  • No incremental transfers. Everlock keeps no IXFR journal, so every transfer is the full zone. An incoming IXFR request is answered — with a full transfer (RFC 1995 §4 fallback) — so secondaries that prefer IXFR (most hosted providers) work fine; they just receive the whole zone each time. Fine for small zones.
  • Stable SOA serial. The serial only advances when the zone's effective content changes, so redundant reloads don't NOTIFY-storm your secondaries. A consequence: a bare /dns reload is not a way to force a re-transfer — make a real change instead.
  • Replication is fully CLI-driven. /dns zones create <zone> [mode=primary|secondary] picks the zone mode (default primary), and /dns peer keygen|list|create|set|delete manage the peers. Every one of these writes the zone TOML and reloads for you; editing the TOML by hand and running /dns reload remains equivalent if you prefer it. The only replication knob still file-only is the runtime address_mode/listen config in everlock-system/config/dns.toml.

See the internal design doc for the full list and the server-replication design for the deferred-work table.


dns howto nat replication vps