Running Multiple Proxy Instances

Running a single NetBird proxy instance works well for many deployments, but production environments often require higher availability. By deploying multiple proxy instances across separate servers, you get automatic failover - if one instance goes down, the remaining instances continue serving traffic without interruption.

This guide covers how to deploy additional proxy instances, manage tokens and TLS certificates across instances, and monitor your proxy cluster.

How clustering works

                     ┌── HA Cluster ──────────────┐
      Auth Check     │  *.eu.proxy.example.com    │
  ◄─────────────────►│                            │
  │                  │  Proxy W (TLS)             │ ◄── WireGuard ──┐
  │                  │  Proxy X (TLS)             │ ◄── WireGuard ──┤► NetBird Peer
  │                  │  Proxy Y (TLS)             │ ◄── WireGuard ──┘   (Service)
  │                  └────────────────────────────┘
  │
Management Service
(netbird.example.com)

Proxy instances that share the same NB_PROXY_DOMAIN value automatically form a cluster. Each instance independently connects to the management server via gRPC and registers itself under the shared domain. The management server tracks all connected instances and distributes traffic configuration to each one.

When a client connects to a service domain (e.g., myapp.proxy.example.com), DNS resolves to one of the servers running a proxy instance. That instance handles TLS termination and forwards the request through the WireGuard mesh to the target. If the server becomes unavailable, clients are routed to another server in the cluster through DNS failover.

There is no leader election or instance-to-instance communication - each proxy instance operates independently and receives the same configuration from the management server.

Prerequisites

Before deploying additional proxy instances, make sure you have:

  • A working single-instance proxy deployment (see Enable Reverse Proxy if you haven't set this up yet)
  • One or more additional servers with Docker installed
  • Traefik configured on each server (or a load balancer in front of all servers)
  • The ability to update DNS records to point to multiple servers
  • Access to the management server CLI to generate proxy tokens

Token management

Each proxy instance authenticates with the management server using an access token. You can either generate a unique token per instance or share a single token across instances.

Recommended: one token per instance. Using unique tokens provides better auditability (you can see which instance connected) and allows you to revoke access for a single instance without affecting others.

Generate a token for each instance. The command differs depending on whether you use the combined container or the older multi-container setup.

Combined container (netbirdio/netbird-server):

docker exec -it netbird-server /go/bin/netbird-server token create \
  --name "proxy-server-1" --config <netbird-data-dir>/config.yaml

docker exec -it netbird-server /go/bin/netbird-server token create \
  --name "proxy-server-2" --config <netbird-data-dir>/config.yaml

Multi-container (separate netbirdio/management image):

docker exec -it netbird-management /go/bin/netbird-mgmt token create --name "proxy-server-1"
docker exec -it netbird-management /go/bin/netbird-mgmt token create --name "proxy-server-2"

Use a descriptive --name for each token so you can identify which instance it belongs to when listing or revoking tokens:

# List all tokens (combined container)
docker exec -it netbird-server /go/bin/netbird-server token list \
  --config <netbird-data-dir>/config.yaml

# List all tokens (multi-container)
docker exec -it netbird-management /go/bin/netbird-mgmt token list

# Revoke a specific instance's token (combined container)
docker exec -it netbird-server /go/bin/netbird-server token revoke <token-id> \
  --config <netbird-data-dir>/config.yaml

# Revoke a specific instance's token (multi-container)
docker exec -it netbird-management /go/bin/netbird-mgmt token revoke <token-id>

Deploying additional instances

On each additional server, set up a proxy instance using the same configuration as your first instance, but with its own token.

Step 1: Create the proxy environment file

Create a proxy.env file on the new server:

NB_PROXY_DOMAIN=proxy.example.com
NB_PROXY_TOKEN=nbx_unique_token_for_this_instance
NB_PROXY_MANAGEMENT_ADDRESS=https://netbird.example.com:443
NB_PROXY_ADDRESS=:8443
NB_PROXY_ACME_CERTIFICATES=true
NB_PROXY_ACME_CHALLENGE_TYPE=tls-alpn-01
NB_PROXY_CERTIFICATE_DIRECTORY=/certs

The NB_PROXY_DOMAIN value must match across all instances in the cluster. Use a unique NB_PROXY_TOKEN for each instance.

Step 2: Create the Docker Compose file

Create a docker-compose.yml on the new server:

services:
  traefik:
    image: traefik:v3.4
    container_name: traefik
    restart: unless-stopped
    command:
      - "--entrypoints.websecure.address=:443"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
    ports:
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks: [netbird]

  proxy:
    image: netbirdio/reverse-proxy:latest
    container_name: netbird-proxy
    restart: unless-stopped
    networks: [netbird]
    depends_on:
      - traefik
    env_file:
      - ./proxy.env
    volumes:
      - netbird_proxy_certs:/certs
    labels:
      - traefik.enable=true
      - traefik.tcp.routers.proxy-passthrough.entrypoints=websecure
      - traefik.tcp.routers.proxy-passthrough.rule=HostSNI(`*`)
      - traefik.tcp.routers.proxy-passthrough.tls.passthrough=true
      - traefik.tcp.routers.proxy-passthrough.service=proxy-tls
      - traefik.tcp.routers.proxy-passthrough.priority=1
      - traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443
    logging:
      driver: "json-file"
      options:
        max-size: "500m"
        max-file: "2"

networks:
  netbird:

volumes:
  netbird_proxy_certs:

Step 3: Start the instance

docker compose pull
docker compose up -d

# Verify the proxy is running
docker compose logs -f proxy

You should see log messages indicating the proxy has connected to the management server and registered under the cluster domain.

Step 4: Update DNS

Add DNS records so that your proxy domain resolves to all servers running proxy instances. The simplest approach is multiple A records:

TypeNameContent
Aproxy.example.com<server-1-ip>
Aproxy.example.com<server-2-ip>
A*.proxy.example.com<server-1-ip>
A*.proxy.example.com<server-2-ip>

Most DNS providers perform round-robin resolution when multiple records exist for the same name. If a server becomes unreachable, clients retry against the next resolved IP.

Alternatively, place a load balancer in front of all proxy servers and point DNS to the load balancer's address.

TLS certificate management

Each proxy instance needs valid TLS certificates for the service domains it handles. There are two approaches, and the right choice depends on your cluster size and operational preferences.

ACME mode (Let's Encrypt)

With NB_PROXY_ACME_CERTIFICATES=true, each instance independently provisions its own certificates from Let's Encrypt. This is the simplest approach - no certificate files need to be shared between servers.

Each instance completes the ACME challenge independently using the configured challenge type (tls-alpn-01 or http-01). This means each server must be reachable on the challenge port for its own domains.

Static certificates (wildcard)

For larger clusters or environments where you want consistent certificate management, use a wildcard certificate shared across all instances. This avoids ACME rate limits and removes the need for each server to complete ACME challenges.

The recommended approach is to store your wildcard certificate and key on a shared NFS mount that all proxy servers can access. Cloud providers offer managed NFS services for this purpose - for example, AWS Elastic File System (EFS), Google Cloud Filestore, or Azure Files. Mount the shared filesystem to the same path on each server, then configure the proxy to read certificates from that path:

NB_PROXY_DOMAIN=proxy.example.com
NB_PROXY_CERTIFICATE_FILE=tls.crt
NB_PROXY_CERTIFICATE_KEY_FILE=tls.key
NB_PROXY_CERTIFICATE_DIRECTORY=/certs

Mount the shared NFS directory into the container as a read-only bind mount:

volumes:
  - /mnt/shared-certs:/certs:ro

With this setup, you only need to update the certificate files in one place. When you renew or rotate a certificate on the NFS share, every proxy instance picks up the change automatically.

Choosing between ACME and static certificates

ACME (Let's Encrypt)Static (wildcard)
Setup complexityLower - no certificate distribution neededHigher - requires a shared NFS mount across servers
Certificate rotationAutomaticUpdate once on the shared mount; all instances reload automatically
ACME rate limitsEach instance provisions independently; risk of rate limits at scaleNot applicable
Best forSmall clusters (2–3 instances)Larger clusters or strict certificate control

Monitoring and failover

Verifying cluster status

After deploying all instances, verify they are connected:

  1. Open the NetBird dashboard and navigate to Reverse Proxy > Services
  2. Click Add Service and check the domain selector - your cluster domain should appear with a Cluster badge
  3. The management server tracks all connected proxy instances; if an instance disconnects, it is removed from the active cluster

Checking proxy logs

On each server, check the proxy logs for connectivity and error information:

docker compose logs -f proxy

Look for connection status messages confirming the proxy is connected to the management server and receiving configuration updates.

Failover behavior

When a proxy instance goes down:

  • DNS-based failover directs clients to remaining healthy servers (depending on your DNS configuration and TTL settings)
  • The management server detects the disconnected instance and removes it from the active cluster
  • Remaining instances continue serving all configured services without interruption
  • When the instance recovers and reconnects, it automatically rejoins the cluster and begins serving traffic again

No manual intervention is required for failover or recovery.

Removing an instance

To gracefully remove a proxy instance from the cluster:

# Stop the proxy on the server being removed
docker compose down

# Revoke its token on the management server (combined container)
docker exec -it netbird-server /go/bin/netbird-server token list \
  --config <netbird-data-dir>/config.yaml
docker exec -it netbird-server /go/bin/netbird-server token revoke <token-id> \
  --config <netbird-data-dir>/config.yaml

# Revoke its token on the management server (multi-container)
docker exec -it netbird-management /go/bin/netbird-mgmt token list
docker exec -it netbird-management /go/bin/netbird-mgmt token revoke <token-id>

After stopping the instance, update your DNS records to remove the server's IP address so clients are no longer directed to it.