Skip to content

Add a Server (Relay)

What this is: bringing a new relay onto the network. A "server" here is the box in the middle that passes everyone's messages around, stores chat and drawings, and covers a patch of ground. When you'd do it: standing up a new site, adding capacity, or covering a new area. How long it takes: an afternoon for the box itself; the part you do on the Directory is a couple of minutes.

This one is different from the rest of this guide. It isn't a single web form. Two people are involved:

  • a deployment engineer (your IT/cloud person) who stands the box up, and
  • an Admin who authorises the new server from the Directory.

If you're the operator and not the engineer, your job is mostly Step 3 — the rest is here so you understand what your engineer is doing and why.

Who can do this: an Admin authorises the server (only an Admin can register a new one). Standing up the box needs a deployment engineer with rack access (or the Linux host, for the standalone paths). You need both.

The shape of it

flowchart LR
    A["Engineer stands<br/>up the box"] --> B["Admin registers it<br/>in the Directory<br/>(address, role,<br/>coverage area)"]
    B --> C["Directory issues a<br/>ServerToken"]
    C --> D["Token goes on<br/>the box; server boots"]
    D --> E["Server joins &<br/>relays registered families"]

The box can't do anything until it has a ServerToken — a signed permit from the Directory that says who it is, the highest classification it may retain and replay, and which cells admit ordinary position and heartbeat publication. The engineer builds the box; the Admin issues the token; the token goes on the box; the server comes to life.

Before you start

  • An Admin login to the Directory.
  • A deployment engineer with rack access (or a Linux host, for the standalone paths).
  • Decide four things up front (see Step 2 below): the server's address (hostname), its coverage area, its classification ceiling, and its token validity period. All four are fields on the Create Device form.

Step 1 — Stand up the box

The server is one program. There are three ways to run it; pick one. On the PETRA rack, use the rack path — it is the supported, repeatable one.

Routers run on core-02, deliberately not on the Directory's box: under the PETRA trust model a router is expendable and content-blind, so a compromised router is a compromised router and not a compromised Directory.

There is one router, on core-02, listening on 7447 (TLS and QUIC) with its plaintext health endpoint on 9090. Standing it up is one playbook:

ansible-playbook playbooks/router.yml

roles/service/server mints the router's TLS leaf from the shared rack CA — one CA across every box and service, not a self-signed cert per box — so certificates are not a manual step here.

What it will not do is invent an identity. Until all three Directory-minted credentials are present the role skips itself, writes no Compose fragment, and leaves the router out of the Compose project rather than failing the run. That is the expected state on a first bring-up: stand the Directory up, mint the credentials (Step 3 below), stage them, and re-run.

Bundle file What it is
service-token.bin service IdentityToken — the router's bearer for Directory REST
server-token.bin the ServerToken — peer-facing attestation, carries coverage_cells and the replay public key
signing-private-key.bin the private Ed25519 seed used only for retained-page receipts

Rack automation must stage all three together and call the Server's bootstrap-credentials command. It must skip the router when any file is absent; copying two files or writing directly into current is not a supported partial-start path.

The hostname is not a free choice: server_hostname is embedded in the ServerToken the Directory mints, and the router presents it as its identity. The TLS leaf's CN, the locator the Directory hands to devices, and the endpoints the web tier's bridge dials must all agree with it, or verification fails.

Option B — Docker

docker build -t server:latest .
docker run -d --name server \
  -e WAYPOINT_LISTEN="tls/0.0.0.0:7447" \
  -e WAYPOINT_DIRECTORY_URL="https://<your-directory-host>" \
  -e WAYPOINT_TLS_CERT_PATH="/etc/waypoint/certs/server.crt" \
  -e WAYPOINT_TLS_KEY_PATH="/etc/waypoint/certs/server.key" \
  -e WAYPOINT_TLS_CA_PATH="/etc/waypoint/certs/node-ca.crt" \
  -e WAYPOINT_SERVICE_IDENTITY_TOKEN_PATH="/etc/waypoint/credentials/current/service-token.bin" \
  -e WAYPOINT_SERVER_TOKEN_PATH="/etc/waypoint/credentials/current/server-token.bin" \
  -v waypoint-credentials:/etc/waypoint/credentials \
  -v waypoint-data:/var/lib/waypoint \
  -v $(pwd)/pki:/etc/waypoint/certs:ro \
  -p 7447:7447 -p 9090:9090 \
  server:latest

Full reference: DOCKER.md in the server repo.

Option C — Native Linux binary

cargo build --release
./target/release/server --config /etc/waypoint/config.toml

A minimal /etc/waypoint/config.toml sets the listen locator, the TLS cert/key paths, the data directory, the Directory URL, and the active credential-generation root. The server repo README has a ready-to-edit example.

What every box needs (all three options)

Thing Where it goes What it's for
TLS cert + key + peer CA /etc/waypoint/certs/ how clients and peer servers trust this box
Service identity token /etc/waypoint/credentials/current/service-token.bin the server's own login to the Directory
ServerToken /etc/waypoint/credentials/current/server-token.bin the permit with its classification ceiling, coverage area, and replay public key
Replay signing key /etc/waypoint/credentials/current/signing-private-key.bin signs only authenticated retained-page completion receipts
Data directory /var/lib/waypoint stored chat/drawings — must be on an encrypted (LUKS/SED) disk
Listen port 7447 (and health on 9090) where clients connect

The Server's registered field-client wildcard subject is a coarse reachability boundary. Keep its actions and key expressions on the closed allowlist; do not add a recursive waypoint/** grant. A Web bridge may use mTLS as extra transport admission defense where deployed, but Directory tokens remain the application identity and authority boundary.

Security note: the data directory holds message content and must sit on an encrypted volume. Don't skip this — see the deployment guide in the server repo.

Step 2 — Decide what to register

When the Admin registers the server, the Directory mints its ServerToken from these details. You enter all of them on the form — agree them with your engineer first:

Setting Plain meaning Example
Server Address the hostname (or IP) clients reach it on; this is the form's Server Address field relay-north.example.mil
Coverage area the geographic cells it serves (see How geofencing works below) gcpuv, gcpuy, gcpvh
Classification ceiling the highest classification this Server may retain and replay Secret
Token validity (days) how long the ServerToken stays valid before you reissue 30

A note on each of the two you might not expect:

  • Classification ceiling: pick it from the form's Classification dropdown (Unclassified → Top Secret). This sets the server's ceiling directly — there is no separate enrolment API. The level is stored on the device, so Reissue Credentials keeps it (and you can change it later by editing the device). A server left at Unclassified can retain and replay only Unclassified values. Native LIVE Relay remains byte-transparent; each receiver applies the signed subject and capability ceilings before decode.
  • Token validity (days): the form's ServerToken validity field, 1–365 days (defaults to 30). The value is stored on the device, so reissuing reuses it. Renew by reissuing the token (Step 3) before it expires.

(Your engineer may also supply the box's IP addresses so they can be baked into the certificate — they'll know if that's needed.)

Step 3 — Register it (Admin) and put the token on the box

Registering a server is the same admin flow as creating a device — a server is just a device with a service role. There is no separate "servers" page; the Admin creates it under Devices, and because it has a server/gateway platform and an address, the Directory mints its ServerToken automatically alongside the device's identity. (See Onboard a device for the full screen-by-screen walkthrough of this form.)

  1. An Admin creates the server in the Directory. In the top menu, click Devices, then Create Device, and fill in the details from Step 2:
  2. Callsign — a name for the box (e.g. RELAY-NORTH).
  3. Server Address — the box's DNS hostname or IP (e.g. relay-north.example.mil). This field is required to mint a ServerToken — leave it blank and you only get an identity token, no ServerToken.
  4. Platform Type — pick server (or gateway for an interop bridge). Only these two platform types receive a Directory-signed identity and a ServerToken; the others (drone/sensor/feed) onboard with a one-time registration token instead.
  5. Role — set to Server (or Gateway).
  6. Coverage Area — click the cells this relay serves on the map, or type geohash-5 cells. At least one cell is required.
  7. Classification — the Server's retained-storage ceiling (Unclassified → Top Secret). Retained ingest or replay above this level is denied, so set it to the highest level this Server must store. Required.
  8. ServerToken validity (days) — how long the token stays valid before reissue (1–365, default 30).

Click Create Device. The Directory signs the ServerToken from the address, role, coverage cells, classification ceiling, and validity you entered.

  1. Save the credentials — they're shown once. The next page (Device Created) shows three credentials: the Identity Token, Server Token, and Signing Key. Click Download Bundle (.tar.gz) to grab all three at once (it also includes a README.txt with the deploy paths), or download each .bin individually. They cannot be recovered — if you navigate away without saving them, you must Reissue Credentials from the device's page, which mints fresh tokens.

The server signing key has one accepted purpose: RetainedReplayReceiptV2. Its public half is bound into both the service identity and the Directory-signed ServerToken. The Server receives no endpoint publication capability and cannot mint authority.

  1. The engineer activates one credential generation. From the bundle, run the server's bootstrap-credentials command with service-token.bin, server-token.bin, and signing-private-key.bin. The command stages a versioned generation and atomically switches the current pointer. On normal startup, the Server loads that one generation and verifies the three-way identity/key binding before it opens any authority or transport surface. Do not copy individual files into current.

On the cloud path these are uploaded into the server's secret slots instead — the VM fetches them on boot.

  1. Restart the server so it picks up the token. On the cloud path that's a VM reset; on Docker/Linux, restart the container/process.

To rotate later (token expiring, or coverage area changing), open the server's device page and click Reissue Credentials to mint a fresh three-file generation, then activate it and restart. Reissuing keeps the device's stored classification ceiling and validity — to change either, edit the device (which updates the stored values) and then reissue. (Note: reissuing also rotates the device's underlying keypair and revokes the previous one, so deploy all three files together.)

How geofencing works

Bedrock is split into cells. Every position, heartbeat, drawing, and target is tagged with the cell it happened in, and message routing is cell-first — the cell is the first thing in the address (waypoint/<cell>/...). See the wire protocol.

A cell is a 5-character geohash — a standard way of chopping the world into a grid. Five characters is roughly a 5 km × 5 km box. Nearby places share a prefix, so cells in the same region look similar (e.g. gcpuv, gcpuy).

A server's coverage area is the list of cells in its ServerToken. At the transport boundary it adds only ordinary field position and heartbeat publication for those cells. Drawings and targets use the closed all-cell retained transport families because the Directory-issued Web machine profile must publish and record the whole operating picture; Directory capabilities, proofs, and receiver checks still decide which authenticated subject may use each cell. The same Web profile permits position reads across canonical cells. So:

  • To accept ordinary field position and heartbeat publication across a wider area, list more cells.
  • A field unit moving into a new patch only publishes through a server that covers that patch's cell.
  • Two servers covering neighbouring areas hand off cleanly because each owns its own cells.

Picking cells: take the area you need to cover, find the geohash-5 cells that overlap it (any geohash tool will show the grid), and list them as the coverage area in Step 2. If in doubt, start with the cells around your operating area and add neighbours later by reissuing the token.

How to know it worked

  • The box's health check answers on port 9090.
  • Clients in the covered area can connect and see each other.
  • Field position/heartbeat traffic for the covered cells flows through the new server, and the Web HQ all-cell profile works without a manual cell list.

If something goes wrong

  • The server starts but rejects traffic, or won't carry some messages. Usually the active three-file generation is incomplete, mismatched, or expired — reissue it from the device page and restart. If retained ingest or replay is denied because its classification is above the Server's ceiling, the Server's Classification is set too low: edit the device, raise the Classification to the level it must carry, then Reissue Credentials and redeploy.
  • Clients in an area don't show up. That area's cell probably isn't in the coverage list. Add it, reissue the token, restart.
  • It won't start at all. Check the TLS certificates and the data-directory mount first — those are the common culprits. The server repo's deploy guide has the checks.
  • Registering a server is one-way — there's no "unregister". To retire one, stop the box and (if needed) revoke its identity.

See also


Verified on 2026-09-08 against the coordinated hard-cut candidate revisions common@ec474fed27d0c72406cab407cd3d1205e4e2a789, directory@6ac4bcf075b03936edb697ec1df4b2d6e9e6f86e, web@33a63e318486d5f7ed17eb54ca0569c9b76800a3, server@6e89eb64242bf75049fb3d02fd1e6362a406baaa, infrastructure@d0a6ee2b378e36794a7e002087a98ad93e548a90, gateway@25ae318331017608b3f0dcafee367fc97cd78d95, node@71299171852e5646a450afad61c7f00af9d59ea0, and android@7353498bed27f72f9ca323e2801c1a5ac2b7c34a. These are source-review candidates, not evidence of live production behavior. This procedure remains gated on immutable-revision review and CI, merge, replacement artifact publication and release locking, rack deployment, and the integrated DDIL runout.