Skip to content

Mesh networking

The mesh subsystem gives you a flat overlay across all your gateways and any clients you’ve joined. You can use it for remote management (SSH into a Pi at home from a laptop on the other side of the world) or for traffic (use a peer node as your egress).

The Gateway supports two control planes:

  • Tailscale — SaaS coordinator, zero setup, free tier. Easy mode.
  • Headscale — self-hosted coordinator, fully under your control. The installer can bring it up alongside gatewayd.

The dataplane in both cases is the same WireGuard, with STUN / DERP relays for NAT traversal.

  • Peer-to-peer reachability between every node you’ve joined to the mesh, regardless of NAT.
  • Subnet routing — advertise your LAN (192.168.10.0/24) from one gateway and reach it from another.
  • Exit nodes — route a remote peer’s egress through your gateway (useful for routing a travel laptop through home).
  • Hostname discovery — every node has a stable hostname inside the mesh DNS (*.headscale.lan or *.ts.net).
  • Per-peer ACLs — Headscale supports Tailscale-style ACL files; rules apply per-source / per-destination.

The full install profile sets up Headscale alongside gatewayd. The bootstrap config:

mesh:
enabled: true
provider: headscale
server_url: "https://mesh.example.invalid:8085"
api_key: "REDACTED"
auth_key: "REDACTED"
namespace: "default"
poll_interval: 30
advertise_routes:
- "192.168.10.0/24"
exit_node: false

The Headscale instance runs as its own systemd unit; gatewayd only consumes its API.

mesh:
enabled: true
provider: tailscale
auth_key: "tskey-auth-REDACTED"
advertise_routes:
- "192.168.10.0/24"

Pick one provider at startup; switching requires a daemon restart with the new bootstrap config.

Two related things:

  • Mesh is overlay connectivity. Any device with tailscale / tailscale up joins.
  • Federation is configuration sharing between gatewayd instances. It usually runs over a mesh tunnel, but it’s a separate concern: federation is about which config kinds flow between which peers; mesh is about whether boxes can reach each other.

A common deployment: mesh joins five nodes; federation has one node push routing and dns to the other four.

Method Path Purpose
GET /api/v1/mesh/status Coordinator reachable? Local node state.
GET /api/v1/mesh/config Current mesh configuration
GET /api/v1/mesh/peers List peers with IP, hostname, RTT, online
DELETE /api/v1/mesh/peers/{id} Remove a peer
POST /api/v1/mesh/up Bring this node up on the mesh
POST /api/v1/mesh/down Take it down
POST /api/v1/mesh/create-key Mint a Headscale pre-auth key
POST /api/v1/mesh/register Register this node with the coordinator
GET /api/v1/mesh/derp-mode DERP-relay mode setting

When the mesh is up and peers are reachable:

  1. Build federation on top. Mesh gives you connectivity; federation lets you push/pull selected config kinds across the same tunnel with per-peer allowlists.
  2. Probe peers from each direction. Symmetric NAT sometimes lets traffic flow one way but not the other — check gwctl mesh peers from both nodes.
  3. Pin DERP if you’re seeing >50 ms RTT to direct-paired peers — a relay can paradoxically be faster than a direct path through a bad route.
  4. Audit the pre-auth keys you’ve minted. A leaked key joins any device to the mesh; revoke via the coordinator and rotate.