guides · self-hosting
Self-host the rendezvous
One small container on any always-on box: deploy it, tell it where it can be reached, then enrol your host with its bundle.
On your own network you don’t need this — clients dial the host directly. The rendezvous is for reaching your machines across the open internet without forwarding a port to the machine you actually use. It introduces two peers, coordinates UDP hole-punching, and only when a direct path is genuinely impossible does it relay — carrying ciphertext it has no keys to open.
You run it yourself, on a Raspberry Pi, a NAS, a home-server box or a VPS.
Read the ordering rule before you start. Deploy and configure the rendezvous first, enrol its bundle into your host second, and mint pairing tokens last. A token minted before the host knows about a rendezvous carries no way to reach you from outside, and the client will simply time out with nothing to explain why. Details in step 5.
What you need
- Any always-on machine with Podman or Docker. A Raspberry Pi is plenty.
- One forwarded UDP port on your router — 48020 — pointed at that machine. This is the port you forward instead of exposing your desktop.
- The relay port range, UDP 48030–48069, if you want relay fallback to work.
1. Run the container
The image is public — no account, no login. It is published for both amd64 and arm64 under one tag, so a Pi and a VPS use the same command.
podman pull registry.gitlab.com/golubaca/nearzero-rendezvous:0.9
# or: docker pull registry.gitlab.com/golubaca/nearzero-rendezvous:0.9
:0.9 is the rendezvous’s line. It follows every release on that line, so you get every fix and never cross a break; this is the tag to use. Each release also has its own tag (:0.9.0, …) if you’d rather pin one exact build. :latest exists too, but it moves to a new line the day one is released, which would cut a long-running server off from every device that has not updated yet. Don’t use it for a server you leave running.
The rendezvous kit on the download page has both ways of running it: a Quadlet unit (rootless Podman under systemd, with automatic updates) and a compose.yaml. Unpack it and use one.
Podman (Quadlet) — from the unpacked rendezvous/ folder:
mkdir -p ~/.config/containers/systemd
cp nearzero-rendezvous.container nearzero-rendezvous.volume ~/.config/containers/systemd/
systemctl --user daemon-reload
systemctl --user start nearzero-rendezvous
loginctl enable-linger $USER # keeps it running after you log out, and across reboots
Docker (or podman-compose) — from the same folder:
docker compose up -d
Every nearzero-rendezvous … command in this guide runs inside the container, so prefix it with podman exec nearzero-rendezvous (or docker exec nearzero-rendezvous). Check it came up:
podman exec nearzero-rendezvous nearzero-rendezvous status
(The kit also has an install.sh for running the server without containers, but it builds from source, which needs access to the private repository — use the image.)
On first run it mints its own identity and a cluster secret. That secret is what makes the forwarded port silent to internet scanners: registration is closed by default, so a device without the secret fails the Noise handshake before the server learns anything about it.
2. Tell it where it can be reached
A fresh server will tell you it has nothing to advertise:
WARN no endpoints configured — `endpoint set <name> <host:port>`.
The server runs, but `bundle` cannot tell devices where to find it.
That’s expected. How many addresses you set depends on where you put the server, and the names (lan, public) are just labels you choose — what matters is that every address you add is one some device can actually dial.
On a VPS
A rendezvous on a VPS shares no network with anything, so it needs exactly one address — the public one:
nearzero-rendezvous endpoint set public rdv.example.com:48020
Don’t add a private address here. Nothing can reach a VPS’s 10.x interface, and advertising one only gives devices a candidate that will never connect.
On a box at home (Pi, NAS, home server)
Here the server sits on the same network as your host, so two addresses earn their keep — neither reaches both populations. A device on your own LAN can’t use your public address, and a device in a café can’t use 192.168.x.x:
nearzero-rendezvous endpoint set lan 192.168.1.100:48020
nearzero-rendezvous endpoint set public home.example.duckdns.org:48020
Use a dynamic-DNS name for the public one if your ISP rotates your IP. Devices resolve it at connect time, so the name keeps working after your address changes — a literal IP does not.
Either way
Restart the server so it advertises what you set, and confirm:
nearzero-rendezvous endpoint list
A correctly configured server names them on startup:
endpoints=192.168.1.100:48020, home.example.duckdns.org:48020
public addresses resolved — will synthesize punch candidates for LAN-local devices
That second line is the one to look for. It means devices are handed public candidates even when they’re sitting next to the server, so a laptop that starts at home and moves elsewhere keeps working.
3. Print the bundle
The enrollment bundle is a single line that carries the endpoints, the server’s public key and the cluster secret. It is how a device learns this rendezvous exists.
nearzero-rendezvous bundle
No arguments — the server remembers what you configured. It prints a nearzero-rdv2:… string and a QR code you can scan from a phone. Add --no-qr if you’re capturing stdout in a script.
Treat the bundle like a Wi-Fi password. Anyone holding it can register with your rendezvous. It does not let them into your desktop — pairing and the connection password are separate — but there’s no reason to paste it anywhere public.
If you ever move the server to a new address, re-run bundle with --endpoint <host:port>; that updates the stored value so you don’t have to re-enrol every device.
4. Enrol the bundle into your host
On the host, open the admin UI at https://127.0.0.1:48011/ and go to Settings → Rendezvous. Paste the whole nearzero-rdv2:… line and save.
The host connects out to the rendezvous and stays registered; nothing needs forwarding to the host itself. Check the page shows the rendezvous as connected before moving on.
If you replace an already-saved bundle while a host is running, the page will tell you the running host still holds the old one — re-paste to apply it live, or restart the host.
5. Mint tokens last
This is the step order that matters. Pairing tokens are generated on the host, under Pairing, and each one is a self-contained offer: it carries how to reach this host, including the rendezvous details the host knew at the moment you minted it.
Mint a token before enrolling a bundle and the host tells you plainly:
Local network only. No rendezvous is enrolled, so this code carries no way to reach this host from elsewhere.
Enrol a rendezvous that only has a private address and you get the sharper version:
This code only works on your own network. Your rendezvous is enrolled under a private address, so a device somewhere else cannot reach it and pairing will simply time out with nothing to explain why.
Both are recoverable — fix the address, restart the rendezvous, wait about a minute for the host to reconnect, and mint a fresh token. But tokens already handed out don’t retroactively gain the rendezvous, which is why it’s cheaper to do it in order:
- Deploy the rendezvous.
- Give it a public address — plus a LAN one if it lives on your own network.
- Print its bundle.
- Enrol the bundle in the host.
- Then mint tokens and pair your clients.
What the server can see
Signaling only: device identifiers, public keys and current addresses — enough to introduce two peers. Session keys are negotiated end-to-end between host and client, so the rendezvous never holds them and moves ciphertext blindly even in relay mode. A compromised rendezvous could disrupt connections, not watch them.
Operator notes
- The kit’s Quadlet unit and
compose.yamlboth follow:0.9. The Quadlet unit hasAutoUpdate=registry, sopodman auto-updatetakes every new release on the line; with Compose,docker compose pullfollowed bydocker compose up -ddoes the same. The image is public, so neither needs a stored login. - Mind the rendezvous line, not the release number. Each host and client line works with exactly one rendezvous line, and the server and every device must match, or they are refused at connect. Today hosts and clients 0.9 work with rendezvous 0.9; which versions work together lists the pairs. A break between hosts and clients leaves the rendezvous alone, so the server needs nothing then.
- At a rendezvous break, change one tag. The rendezvous line moves only when its own wire breaks, and that moves hosts and clients to a new line too; the release notes say so at the top. Then edit the image tag to the new line,
Image=innearzero-rendezvous.containerorimage:incompose.yaml, restart the server (systemctl --user daemon-reloadandsystemctl --user restart nearzero-rendezvous, ordocker compose up -d), and update your hosts and clients. That tag is the only edit. - Repeated reconnects — a flaky Wi-Fi uplink, say — are expected traffic and are not rate-limited away.