feat(netbird): relay + STUN cluster members on the netcup proxy hosts #412

Merged
k.renner merged 2 commits from netbird-netcup-relays into master 2026-07-26 06:27:35 +00:00
6 changed files with 79 additions and 1 deletions
@@ -17,3 +17,8 @@ netbird_proxy_domain: "proxy.renner.uno"
# Reuse VPS-1's proxy token — a shared NB_PROXY_TOKEN across members with the
# same NB_PROXY_DOMAIN is a valid HA cluster, so no separate token is needed.
netbird_proxy_token: "{{ vault_netbird_proxy_token }}"
# Relay auth secret — MUST be identical across every relay instance and the
# management server's Relay.Secret (same vault var as the netbird-server
# role); credentials minted by management only validate against it.
netbird_relay_secret: "{{ vault_netbird_relay_secret }}"
@@ -10,3 +10,8 @@
ansible.builtin.command:
cmd: docker compose up -d --force-recreate proxy
chdir: /opt/netbird-proxy
- name: Restart netbird relay
ansible.builtin.command:
cmd: docker compose up -d --force-recreate relay
chdir: /opt/netbird-proxy
@@ -63,6 +63,13 @@
mode: "0600"
notify: Restart netbird proxy
- name: Deploy relay.env (contains the shared relay auth secret)
ansible.builtin.template:
src: relay.env.j2
dest: /opt/netbird-proxy/relay.env
mode: "0600"
notify: Restart netbird relay
- name: Pull reverse-proxy image
ansible.builtin.command:
cmd: docker compose pull
@@ -38,6 +38,34 @@ services:
max-size: "500m"
max-file: "2"
# Relay cluster member — additional NetBird relay (+ embedded STUN) so
# relayed connections and new-P2P candidate discovery survive a
# controller-VPS (Hetzner) outage. Registered in the management server's
# Relay.Addresses/Stuns (netbird-server role, management.json). Host
# network like the proxy: the ws listener (:33080) and STUN (:3478/udp)
# must sit on the host's un-NATed public addresses — STUN behind NAT
# reports wrong reflexive candidates. Both ports are >1024, so the
# ip_unprivileged_port_start sysctl is not involved.
#
# Deliberately NOT covered by the netbird-proxy-watchdog (which probes the
# proxy's public URL): this relay is a fallback with client-side failover
# to the other relays, so a wedged instance degrades redundancy rather
# than availability; restart: unless-stopped catches crashes. If active
# relay monitoring is wanted later, the binary serves
# http://localhost:9000/health (verified on 0.73.2).
relay:
image: netbirdio/relay:{{ netbird_version }}
container_name: netbird-relay
restart: unless-stopped
network_mode: host
env_file:
- ./relay.env
logging:
driver: "json-file"
options:
max-size: "500m"
max-file: "2"
# netbird-proxy listens on :443 only (NB_PROXY_ADDRESS=:443) and has no HTTP
# listener, so proxy-only hosts leave port 80 unanswered. This did not show
# while proxy.renner.uno still resolved to the controller host, because the
@@ -0,0 +1,21 @@
# NetBird relay cluster member (per docs.netbird.io "Set Up External Relay
# Servers"). Registered in the management server's Relay.Addresses so the
# mesh keeps a working relay when the controller VPS (Hetzner) is down.
NB_LOG_LEVEL=info
NB_LISTEN_ADDRESS=:33080
# Plaintext rel:// (not rels://): Let's Encrypt is impossible on this host —
# :443 belongs to the reverse-proxy (tls-alpn-01) and :80 to the redirect, so
# neither HTTP-01 nor a second ALPN listener can run, and shipping a
# zone-wide DNS-01 token to an internet-facing host is a worse trade than
# plaintext transport. Peer payload stays WireGuard-encrypted end to end;
# the exposure is relay framing metadata and credential sniff-and-reuse
# (bounded by the 24h credential TTL) to an on-path attacker.
NB_EXPOSED_ADDRESS=rel://{{ inventory_hostname }}:33080
# Must equal the management server's Relay.Secret (vault, shared with the
# VPS relay) — mismatches fail silently per upstream docs.
NB_AUTH_SECRET={{ netbird_relay_secret }}
# Embedded STUN: reflexive-candidate discovery must survive a controller
# outage, or no NEW P2P connection can form while Hetzner is down and the
# relay diversity here would be moot for NATed peers.
NB_ENABLE_STUN=true
NB_STUN_PORTS=3478
@@ -3,6 +3,14 @@
{
"Proto": "udp",
"URI": "stun:{{ netbird_server_host }}:3478"
},
{
"Proto": "udp",
"URI": "stun:proxy-1.renner.uno:3478"
},
{
"Proto": "udp",
"URI": "stun:proxy-2.renner.uno:3478"
}
],
"TURNConfig": {
@@ -23,7 +31,11 @@
"URI": "{{ netbird_server_host }}:443"
},
"Relay": {
"Addresses": ["rels://{{ netbird_server_host }}:443"],
"Addresses": [
"rels://{{ netbird_server_host }}:443",
"rel://proxy-2.renner.uno:33080",
"rel://proxy-1.renner.uno:33080"
],
"CredentialsTTL": "24h",
"Secret": "{{ netbird_relay_secret }}"
},