Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions src/pages/selfhosted/maintenance/configuration-files.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -513,13 +513,29 @@ When running behind your own reverse proxy (Nginx, Caddy, Nginx Proxy Manager, e

See [External Reverse Proxy Configuration](/selfhosted/external-reverse-proxy) for detailed templates for Nginx, Caddy, and other proxies.

### Using External Services (Advanced)
### Using Additional or External Services (Advanced)

<Note>
The default NetBird deployment includes embedded relay, signal, and STUN services. External services are only needed for advanced use cases.
</Note>

To use external STUN, relay, or signal servers, add overrides to `config.yaml`:
To add external relays while keeping the embedded relay and STUN service, use `additionalRelays`. Every additional relay must use the same authentication secret as `server.authSecret`:

```yaml
server:
# ... basic settings ...

authSecret: "shared-relay-secret"

# Keep the embedded relay and append these relay addresses.
additionalRelays:
- "rels://relay-us.example.com:443"
- "rels://relay-eu.example.com:443"
```

The automatically generated embedded relay address is advertised first, followed by the entries in `additionalRelays`. This setting adds relay addresses only; it does not advertise STUN listeners from the additional relay servers.

To replace embedded services completely, add external service overrides to `config.yaml`:

```yaml
server:
Expand All @@ -540,6 +556,11 @@ server:
# Optional: Use external signal server
signalUri: "https://signal.example.com:443"
```

<Note>
`additionalRelays` and `relays` are different modes. If `relays.addresses` is configured, it takes precedence and disables the embedded relay. Configuring `stuns` disables the embedded STUN service independently.
</Note>

See the [Scaling Your Self-Hosted Deployment](/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment) guide for more details on configuring external services.

---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,23 @@ The **relay servers** run independently on different machines, each handling rel
| `NB_TLS_KEY_FILE` | No | Path to TLS private key |
| `NB_LOG_LEVEL` | No | Log level: `debug`, `info`, `warn`, `error` |

### Main Server config.yaml - External Services
### Main Server config.yaml - Relay Modes and External Services

To keep the embedded relay and STUN service while adding external relays:

```yaml
server:
authSecret: "shared-secret"

# These addresses are appended after the embedded relay address.
additionalRelays:
- "rels://relay-us.example.com:443"
- "rels://relay-eu.example.com:443"
```

Every additional relay must use the same value in `NB_AUTH_SECRET`.

To replace the embedded relay and STUN service completely:

```yaml
server:
Expand All @@ -69,6 +85,10 @@ server:
# signalUri: "https://signal.example.com:443"
```

<Warning>
Do not combine `additionalRelays` with `relays`. When `relays.addresses` is present, replacement mode takes precedence and the embedded relay is disabled.
</Warning>

## Troubleshooting

### Peers Can't Connect via Relay
Expand Down
105 changes: 89 additions & 16 deletions src/pages/selfhosted/maintenance/scaling/set-up-external-relays.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

import {Note, Warning} from "@/components/mdx";

This guide is part of the [Splitting Your Self-Hosted Deployment](/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment) guide. It covers deploying external relay and STUN servers and configuring your main server to use them.
This guide is part of the [Splitting Your Self-Hosted Deployment](/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment) guide. It covers deploying external relay servers and configuring your main server either to use them alongside the embedded relay or to replace the embedded relay and STUN service completely.

NetBird supports two external relay modes:

- `server.additionalRelays` keeps the combined server's embedded relay and STUN service running and appends external relay addresses.
- `server.relays` replaces the embedded relay. In the combined server, this also disables the embedded STUN service, so replacement STUN addresses must be configured under `server.stuns`.

For each relay server you want to deploy:

Expand All @@ -12,18 +17,23 @@ For each relay server you want to deploy:
- Public IP address
- A domain name pointing to the server (e.g., `relay-us.example.com`)
- Docker installed
- Firewall ports open: **80/tcp** (Let's Encrypt HTTP challenge), **443/tcp** (relay), and **3478/udp** (STUN). If you configure multiple STUN ports, open all of them
- Firewall ports open: **80/tcp** (Let's Encrypt HTTP challenge) and **443/tcp** (relay). Open **3478/udp** only if this external relay also serves STUN; if you configure multiple STUN ports, open all of them

## Generate Authentication Secret

All relay servers must share the same authentication secret with your main server. You can generate one with:
All relay servers advertised by one Management server must use the same authentication secret.

- When keeping the embedded relay, reuse the existing `server.authSecret` value as `NB_AUTH_SECRET` on every external relay.
- When replacing the embedded relay, use the same new value for `NB_AUTH_SECRET` on every external relay and `server.relays.secret` on the main server.

For a new deployment, generate a secret with:

```bash
# Generate a secure random secret
openssl rand -base64 32
```

Save this secret - you'll need it for both the relay servers and your main server's config.
Save this secret securely. Do not commit it to source control.

## Create Relay Configuration

Expand Down Expand Up @@ -56,6 +66,10 @@ NB_STUN_PORTS=3478
Replace `relay-us.example.com` with your relay server's domain and `your-shared-secret-here` with the secret you generated.
</Note>

<Note>
`additionalRelays` advertises relay addresses only. If you keep the embedded STUN service, external relays do not need to run STUN and you can set `NB_ENABLE_STUN=false`. Configuring `server.stuns` advertises external STUN addresses but disables the embedded STUN service.
</Note>

Create `docker-compose.yml`:

```yaml
Expand All @@ -66,7 +80,7 @@ services:
restart: unless-stopped
ports:
- '443:443'
# Expose all ports listed in NB_STUN_PORTS
# Required only when NB_ENABLE_STUN=true. Expose every NB_STUN_PORTS value.
- '3478:3478/udp'
env_file:
- relay.env
Expand Down Expand Up @@ -112,7 +126,7 @@ Verify it's running:
docker compose logs -f
```

You should see:
With `NB_ENABLE_STUN=true`, you should see both services start:
```
level=info msg="Starting relay server on :443"
level=info msg="Starting STUN server on port 3478"
Expand All @@ -139,9 +153,46 @@ If deploying multiple relays (e.g., for different regions), repeat the steps abo

## Update Main Server Configuration

Now update your main NetBird server to use the external relays instead of the embedded one.
Choose one of the following modes. Do not configure both `additionalRelays` and `relays`; when `relays.addresses` is present, replacement mode takes precedence.

### Keep the Embedded Relay and STUN

Use `additionalRelays` to add external relay capacity while keeping the combined server's local relay and STUN service. Keep the existing `authSecret` and `stunPorts` settings:

```yaml
server:
listenAddress: ":80"
exposedAddress: "https://netbird.example.com:443"
metricsPort: 9090
healthcheckAddress: ":9000"
logLevel: "info"
logFile: "console"
dataDir: "/var/lib/netbird"

# Used by the embedded relay and every additional relay.
authSecret: "your-shared-secret-here"

# Keep the embedded STUN service.
stunPorts:
- 3478

# Appended after the automatically generated embedded relay address.
additionalRelays:
- "rels://relay-us.example.com:443"
- "rels://relay-eu.example.com:443"

### Edit config.yaml
auth:
issuer: "https://netbird.example.com/oauth2"
# ... rest of auth config
```

Every external relay's `NB_AUTH_SECRET` must be byte-for-byte identical to `server.authSecret`. The combined server advertises its automatically generated local relay address first, followed by the addresses in `additionalRelays`.

Keep TCP port 443 and UDP port 3478 exposed on the main server because its embedded relay and STUN services remain active.

### Replace the Embedded Relay and STUN

Use `relays` when the main server should advertise only external relays. In the combined server, replacement mode disables both the embedded relay and its STUN listener.

On your main server, edit the `config.yaml` file:

Expand All @@ -150,13 +201,13 @@ cd ~/netbird # or wherever your deployment is
nano config.yaml
```

Remove the `authSecret` from the `server` section and add `relays` and `stuns` sections pointing to your external servers. The presence of the `relays` section disables both the embedded relay and the embedded STUN server, so the `stuns` section is required to provide external STUN addresses:
Add `relays` and `stuns` sections pointing to your external servers. The `authSecret` setting is no longer required when the embedded relay is disabled because replacement relay credentials come from `relays.secret`:

```yaml
server:
listenAddress: ":80"
exposedAddress: "https://netbird.example.com:443"
# Remove authSecret to disable the embedded relay
# authSecret is not required when the embedded relay is disabled.
# authSecret: ...
# Remove or comment out stunPorts since we're using external STUN
# stunPorts:
Expand Down Expand Up @@ -191,7 +242,7 @@ server:
The `secret` under `relays` and the `NB_AUTH_SECRET` on all relay servers **must be identical**. Mismatched secrets will cause relay connections to fail silently.
</Warning>

### Update docker-compose.yml (Optional)
### Update docker-compose.yml for Replacement Mode (Optional)

If your main server was exposing STUN port 3478, you can remove it since STUN is now handled by external relays:

Expand Down Expand Up @@ -225,7 +276,20 @@ docker compose up -d
docker compose logs netbird-server
```

Verify that the embedded relay is disabled and your external relay addresses are listed:
With `additionalRelays`, verify that the embedded relay and STUN service remain enabled:

```
INFO combined/cmd/root.go: Relay: true (log level: info)
INFO combined/cmd/root.go: STUN ports: [3478]
```

The advertised addresses contain the automatically generated embedded relay first and then the additional relays:

```
Relay addresses: [rels://netbird.example.com:443 rels://relay-us.example.com:443 rels://relay-eu.example.com:443]
```

With replacement-mode `relays`, verify that the embedded relay is disabled and only the external relay addresses are listed:

```
INFO combined/cmd/root.go: Management: true (log level: info)
Expand All @@ -239,13 +303,13 @@ Relay addresses: [rels://relay-us.example.com:443 rels://relay-eu.example.com:44

### Check Peer Status

Connect a NetBird client and verify that both STUN and relay services are available:
Connect a NetBird client and verify that its selected STUN and relay services are available:

```bash
netbird status -d
```

The output should list your external STUN and relay servers. All configured STUN servers will appear, but only one randomly chosen relay is used per client:
The `Relays` section shows connections that the client currently maintains; it might not display every relay address at the same time. In replacement mode, an example is:

```
Relays:
Expand All @@ -254,7 +318,16 @@ Relays:
[rels://relay-eu.example.com:443] is Available
```

You can also test failover by stopping one of the relay servers and checking the status again. The client will detect the unavailable server and use the remaining one:
To prove that peer traffic is using a relay, first generate traffic to another peer and inspect that peer in the detailed status output:

```
Connection type: Relayed
Relay server address: rels://relay-eu.example.com:443
```

You can test failover by stopping the relay shown in `Relay server address` while traffic is running. The client detects the outage and reconnects through another advertised relay. Stopping a relay that is not carrying the connection does not interrupt traffic.

After failover, the maintained relay list might look like:

```
Relays:
Expand All @@ -277,4 +350,4 @@ Once confirmed, switch back to normal mode. The client will attempt peer-to-peer

```bash
sudo netbird service reconfigure --service-env NB_FORCE_RELAY=false
```
```
2 changes: 1 addition & 1 deletion src/pages/selfhosted/observability/combined.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ The combined `/metrics` endpoint returns the union of:
- All [Signal metrics](/selfhosted/observability/signal), each one rewritten with a `signal_` prefix so it doesn't collide with the rest of the endpoint. For example, standalone Signal exposes `active_peers` and `messages_forwarded_total`; in the combined container these become `signal_active_peers` and `signal_messages_forwarded_total`.
- All [Relay metrics](/selfhosted/observability/relay) — `relay_*` series. Emitted only when the embedded relay is enabled (no `relays` override set in `config.yaml`).

If you point the combined server at an external Signal (`server.signalUri`) or external Relay (`server.relays`), the corresponding local service is disabled and its metrics will not appear on this endpoint — scrape the external service directly instead.
If you point the combined server at an external Signal (`server.signalUri`) or replace its Relay with `server.relays`, the corresponding local service is disabled and its metrics will not appear on this endpoint — scrape the external service directly instead. Using `server.additionalRelays` keeps the embedded Relay running, so its local metrics remain available here; scrape each additional Relay separately for its own metrics.

## Health endpoint

Expand Down
Loading