SSD Nodes Learn Hosting plans →
How to do am Matt ConnorBy Matt Connor

How to self-host NetBird VPN server for VPS

Set up NetBird on one VPS with DNS, TLS, the pinned quickstart script, setup keys for unattended peers, plus a clear comparison with Headscale.

Wetín self-hosting NetBird VPN server dey give you

Self-hosting NetBird VPN server dey put the control plane for VPS wey you own: na the part wey dey keep peer list, decide which machine fit reach which one, and help two peers find each other behind NAT (network address translation). The tunnels themselves still na WireGuard, and dem dey encrypted directly between your machines. Wetin change be say no outside company dey hold your device inventory or your login flow. Understand clearly wetin this arrangement dey give you, because hosted control plane no dey ever hold the keys wey encrypt your traffic too, and wetin coordination server fit really do if somebody breach am na shorter list than most people dey assume before dem read am.

NetBird dey sit between two things wey you fit already know. E be mesh overlay, so peers dey connect to each other instead of sending everything through one gateway. E also fit self-host from end to end, and this put am against Headscale, the self-hosted Tailscale control server. If na only single-gateway tunnel you don ever run, read the difference between plain WireGuard and mesh overlay first, because na that mental model go make the rest of this page useful.

If wetin you really want na one server wey all your traffic go exit from, mesh na more machinery than the work need. Plain WireGuard VPN for one VPS or Tailscale exit node fit do am with much less thing to run. And if the goal na to reach one private network instead of linking machines to each other, Tailscale subnet router for VPS dey advertise that range to tailnet wey you already get, without any part of the stack below.

Wetin this stack actually dey run

The layout don change recently, and plenty older write-ups still describe the old one. We check am for October 2026 against v0.80.0 and v0.76.2, wey be the release wey this guide pins. The quickstart script dey write one Compose file with three services by default.

  • netbird-server dey carry the management API, signal service, relay with embedded STUN listener, and embedded identity provider. For older releases, dem dey be separate containers, and identity provider na separate Zitadel install wey you need build first.
  • dashboard na the admin web console.
  • traefik dey terminate TLS (transport layer security) and request certificate from Let's Encrypt when e start for the first time.

Two more services dey exist, but dem stay off unless you answer yes for prompt. NetBird Proxy service dey publish internal services for public hostnames. CrowdSec dey filter abusive traffic. You no need either one to build working mesh, and both go use memory for small box.

If you dey come from wg-easy for one Docker container, this one get plenty more parts. Wetin you gain na access policies and per-user accounts, plus peers wey connect directly to each other instead of passing through one gateway.

Wetin you need before you start

A public domain name no be optional. The dashboard, the API and the relay all dey use HTTPS for port 443, and Traefik dey get certificate from Let's Encrypt with the TLS-ALPN-01 challenge. The challenge dey answer for port 443, so you need a name wey public internet fit resolve to this VPS. Bare IP address no go work for this setup.

Create one A record, netbird.example.com pointing to the VPS public IPv4 address, and wait make e propagate before you run anything.

dig +short netbird.example.com

This one suppose print your server address. If you run the installer before DNS propagate, certificate request go fail for first start. Repeated failed validations fit hit Let's Encrypt rate limits, so you go need wait make the limit refill before you try again.

Three ports must dey reachable from internet: TCP 80 for redirect to HTTPS, TCP 443 for dashboard, API, signal and relay traffic plus certificate challenge, and UDP 3478 for STUN.

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 3478/udp
sudo ufw reload
sudo ufw status

Open dem for your provider network firewall too. For most VPS panels, this one na separate control. Na why server wey its own ufw status look correct still fit refuse connections.

STUN (session traversal utilities for NAT) na how peer dey learn the public address and port wey its own NAT assign, so two peers fit try direct tunnel. If you block UDP 3478, peers still go connect through the relay on TCP 443, so nothing go look broken. But you go get Connection type: Relayed for every peer instead, and all traffic go cross your VPS instead of going peer to peer.

For software side, you need Docker with the Compose v2 plugin, plus jq and curl. The script dey check for Docker, Compose and jq, and e go stop if any one dey missing. E dey use curl without checking whether e dey installed, so install am first. If Docker still new for this server, make Docker Compose dey work for the VPS first.

Ports if you skip the bundled reverse proxy

Without Traefik, the combined server still dey carry management, signal and relay for one HTTP backend, so the port list hardly increase. The generated Compose file publishes:

  • TCP 8080, the dashboard, bound to 127.0.0.1
  • TCP 8081, netbird-server, bound to 127.0.0.1
  • UDP 3478, STUN, published directly

Your own proxy go terminate TLS for TCP 443 and forward request to those two local ports. STUN no dey pass through the proxy, so UDP 3478 still need remain open for firewall either way. Guides wey list separate ports for management gRPC, signal gRPC and relay dey describe the older multi-container layout.

Choose this one only when the server already dey terminate TLS for another service. Otherwise, bundled Traefik get fewer rules and fewer mistakes.

Install NetBird server with the quickstart script

The documented one-liner dey pipe the newest release straight enter shell:

curl -fsSL https://github.com/netbirdio/netbird/releases/latest/download/getting-started.sh | bash

Pin am instead. latest dey change, so the same command wey you run two weeks apart go produce two different installs, and nothing for disk go record which one write your config. Download one tagged release, read am, then run am.

mkdir -p ~/netbird
cd ~/netbird
curl -fsSL -o getting-started.sh \
  https://github.com/netbirdio/netbird/releases/download/v0.76.2/getting-started.sh
less getting-started.sh
bash getting-started.sh

The script go first ask for the domain:

Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com):

Then e go ask how TLS go dey handled:

Which reverse proxy will you use?
  [0] Traefik (recommended - automatic TLS, included in Docker Compose)
  [1] Existing Traefik (labels for external Traefik instance)
  [2] Nginx (generates config template)
  [3] Nginx Proxy Manager (generates config + instructions)
  [4] External Caddy (generates Caddyfile snippet)
  [5] Other/Manual (displays setup documentation)
Enter choice [0-5] (default: 0):

Choose [0]. Options 2 through 4 dey write one config snippet and leave the wiring for you, while option 5 dey write only the Compose file and print the setup documentation for terminal. This one correct for machine wey already dey run proxy, but e wrong for fresh machine. Option 0 go then ask for email address, wey Traefik go register with your Let's Encrypt account. The prompt still dey call am address for certificate notifications, but Let's Encrypt stop sending expiry emails on June 4, 2025. So renewal problems go show for Traefik log instead of your inbox.

For first install, answer no to the NetBird Proxy service. E need one extra DNS record: wildcard *.netbird.example.com CNAME wey point to netbird.example.com. E no do anything for ordinary mesh. The CrowdSec prompt only dey appear after you enable the proxy, so if you decline the proxy, e go skip CrowdSec too. You fit add both later.

The script dey write these files for current directory: docker-compose.yml, config.yaml with mode 600, and dashboard.env. If you enable NetBird Proxy, bundled Traefik go add traefik-dynamic.yaml and proxy.env. Treat that directory as state wey you must keep, because config.yaml hold the key wey dey encrypt data for the store. If you lose am, reinstall no go fix the problem.

docker compose ps
docker compose logs -f netbird-server

Every service suppose read running, and server log suppose settle instead of restarting inside loop. Monitor the certificate separately:

docker compose logs traefik | grep -i acme

ACME (automatic certificate management environment) na the protocol wey Traefik dey use to get the certificate. Errors for here almost always mean DNS problem or say port 443 dey closed.

First admin account create

Open https://netbird.example.com. If na fresh install, e go open setup page instead of login form. Enter email address, name, and password, then click Create Account. This one go become the first admin, and the page go redirect you to login form.

That account dey inside NetBird own user store. An identity provider wey dey inside netbird-server container dey power am. Nothing external dey involved. This na the biggest change from self-hosted NetBird of one year ago. That time, working install mean say you first need set up Zitadel or Keycloak, then copy four OIDC (OpenID Connect) values into setup.env before anything fit start at all.

If browser show certificate warning instead of setup page, certificate no issue. Fix am before you continue. Dashboard dey talk to API through the same hostname, and bad certificate fit make e fail for confusing ways.

Join your first peer

Install the client for any Linux machine, including the VPS itself if you want make e join the mesh:

curl -fsSL https://pkgs.netbird.io/install.sh | sh

For Debian and Ubuntu, that script dey configure NetBird package repository, then e install the client through apt. So na the package manager dey manage am either way. If you no like pipe script enter shell, save am first with curl -fsSL -o install.sh https://pkgs.netbird.io/install.sh and read am before you run sh install.sh. Either way, confirm wetin install:

apt-cache policy netbird

netbird na the command line client and the daemon. netbird-ui na the desktop tray app, and headless server no get use for am.

Now point the client to your server:

sudo netbird up --management-url https://netbird.example.com

If you omit --management-url, the client go register with NetBird hosted service, because na the compiled-in default. The command still go succeed, the machine still go get address, and your self-hosted dashboard go remain empty. This one catch almost everybody at least once.

The command go print URL wey you go open for browser to finish the login. After that:

netbird status
ip addr show wt0

Read four lines from netbird status: Management: Connected, Signal: Connected, one Relays: line wey dey report every available relay, and one NetBird IP: for the overlay range. wt0 na the WireGuard interface wey NetBird create, and e suppose carry that same address.

Join second machine without person dey attend

Browser login no go work for machine wey no get browser and nobody dey in front of am. Setup key na pre-authentication token wey register machine without the interactive step. Create one for dashboard under Setup Keys.

Two types dey. One-off key dey authenticate exactly one machine, then e don finish. Reusable key fit register many machines, with optional limit for how many. Both get expiry, and both fit automatically assign new peer to group. This make the access rules for that group apply immediately when machine appear.

sudo netbird up --setup-key <SETUP-KEY> \
  --management-url https://netbird.example.com \
  --hostname build-runner-01

--hostname dey set the name wey dashboard go show. If you no set am, peer go use the name wey machine call itself. Fleet of entries wey all get name ubuntu no help anybody.

For containers and short-lived build agents, mark the key as ephemeral when you create am. Peers wey you register with ephemeral key go remove automatically after dem don stay offline for more than 10 minutes. This one keep dead entries out of peer list.

Understand this limit before you plan around setup keys: when key expire or you revoke am, e go stop new registrations, but e no go disconnect machines wey already register with am. To remove machine access, you must remove that peer.

You still need separate identity provider?

For small installation, no. The built-in user store dey handle accounts wey people create from dashboard, and that one dey enough for small number of users.

You need external identity provider when you already get one and you no want maintain second list of users. NetBird dey accept any provider wey support OIDC. Register confidential OIDC client for your provider, then add am for NetBird dashboard with four values: name, client ID, client secret and issuer. NetBird go give you redirect URL wey you go paste back for the provider. Named integrations dey available for Google, Microsoft Entra ID, Okta, Zitadel, Keycloak, Authentik and Pocket ID. Anything else go enter as generic OIDC. If you already dey run Authentik as your self-hosted single sign-on, na this path go keep one account list instead of two.

Local login still dey available after you add provider, and every provider wey you configure go show for login page. Keep one local admin account with strong password. If OIDC configuration break, you still get way to enter.

NetBird or Headscale: which control plane you suppose run?

Both remove the same dependency: the hosted control server wey your clients for otherwise dey call home. But dem no be the same kind project.

Headscale na reimplementation of the Tailscale control server, and you go continue to use the official Tailscale clients. E no get official web console. You manage users and pre-authentication keys with the headscale command against a config file. Community web interfaces dey, but dem no be part of the project. This one suit people wey want make their state dey inside files and their changes dey for version control.

NetBird ships the complete product: e get its own client, its own dashboard, embedded identity provider, and access policies wey you edit for browser. This one mean more moving parts for your VPS, and e much easier to hand over to colleague wey no go ever open terminal.

Run Headscale if you don already invest for Tailscale clients or you want the smallest control plane wey possible. Run NetBird if several people need manage peers and you want console and SSO without assembling one yourself. Before you commit to either one, check wetin the Tailscale free plan actually cover, because non-commercial group wey fit inside six users no go pay anything for hosted control plane, with unlimited user devices and 50 tagged resources to start, and fit get no reason to run one at all. Once you pass that limit, the bill dey grow based on number of people, not number of machines. So work out wetin Tailscale go charge your group to get figure wey you fit compare with the VPS and the hours wey this stack go cost you.

How small a VPS fit run this?

The documented minimum na 1 CPU and 2 GB of memory. NetBird own notes put the current floor near 1 GB of RAM now wey user management dey local, compared with the 2 GB to 4 GB wey the older layout need when full Zitadel deployment dey part of the stack. Buy 2 GB. The extra headroom na wetin go allow upgrade pull new images while the old ones still dey disk.

Three things safe to leave out for small box. Decline the NetBird Proxy service. E dey publish internal services for public hostnames, and e no get anything to do with peers connecting. Skip CrowdSec too. The script only offer am together with the proxy. E better make you add am later for exposed box instead of day one. Keep the default SQLite store for the netbird_data volume. Move go PostgreSQL only when you split the deployment across machines or encounter real concurrency. Documentation say na migration wey you fit do later.

The relay na the only component wey you no fit remove. Two peers wey their NAT assign different port for every destination no go ever establish direct tunnel. So na the relay be the only path wey fit make dem work at all. Disabling am save very little memory and e break connections for way wey hard to trace.

When one box no longer enough, relays na the first thing to move off am. Standalone relay dey run with NB_LISTEN_ADDRESS, NB_EXPOSED_ADDRESS, NB_AUTH_SECRET and NB_ENABLE_STUN. The shared secret must match exactly for the relay and the main server. Otherwise, clients no go authenticate to am.

Failure modes, and wetin you go see

Dashboard show certificate warning. Traefik no obtain certificate. Run docker compose logs traefik | grep -i acme. Two things fit cause am. Either dig +short netbird.example.com no dey return this VPS yet, or TCP 443 close somewhere between Let's Encrypt and the container, usually for provider network firewall instead of for ufw. Challenge dey answered on 443, so open port 80 no go help here. Fix the cause before you retry am for loop, because Let's Encrypt allow only five failed validations per hostname every hour, and loop fit lock you out from retry until that limit refill.

Client talk say e connect and dashboard empty. Client register with NetBird hosted service because --management-url dey missing. Run netbird status --detail and read the Management: line, wey name the server e really dey talk to. If you see Management: Connected to https://api.netbird.io:443, e mean say e go cloud. Run sudo netbird down, then run sudo netbird up --management-url https://netbird.example.com again.

Every peer show Connection type: Relayed. No direct tunnels dey form, so all traffic dey cross your VPS and add one latency hop. Check UDP 3478 for VPS firewall and provider firewall, because STUN na wetin help peer learn its own public address and port. netbird status --detail also print Direct: false and the ICE (interactive connectivity establishment) candidate types for each peer, wey show how far the attempt reach. For some networks, relayed na the only available outcome and nothing dey wrong.

Peer join and e no fit reach anything. To dey inside mesh no mean say two peers fit talk. Access policies decide that, and group wey no get policy attached no go reach anything. Check the policy for dashboard before you start debug routes and firewalls.

netbird status report daemon problem. The service no dey run. Use sudo netbird service status and sudo netbird service start. Client logs dey for /var/log/netbird/client.log. For anything wey you no fit identify, netbird debug bundle --anonymize --system-info collect logs, status, routes, DNS settings and firewall state enter one archive.

Backups and upgrades

Two things dey carry the whole install: the directory wey hold docker-compose.yml and config.yaml, plus the Docker volume wey hold the database and encryption keys. Back dem up together. config.yaml hold the key wey dey encrypt data for the store, so database copy without am go restore to nothing wey you fit read.

docker volume ls
docker compose down
sudo tar czf netbird-config.tgz -C ~ netbird
docker run --rm -v netbird_netbird_data:/data -v "$PWD":/backup \
  alpine tar czf /backup/netbird-data.tgz -C /data .
docker compose up -d

Compose dey prefix volume names with the project directory, so the volume wey dem document as netbird_data normally dey appear as netbird_netbird_data. Run docker volume ls first and use the name wey e print, or docker run above go silently create empty volume and archive nothing. Keep the archives away from the VPS. If you already get backup tool, restic or BorgBackup fit handle the offsite part.

Upgrading the server na to pull and recreate:

docker compose pull
docker compose up -d
docker compose ps

Before you depend on am, run docker compose config | grep image:. Any tag wey read latest suppose get pinned to a version, for the same reason wey you pin the install script: you need know wetin dey run, and you need get version wey you fit return to when upgrade no behave well. Clients dey upgrade through whichever package manager install dem.

FAQ

I need my own identity provider to self-host NetBird?

No. Current releases get built-in user store, so you fit create the first admin account for browser at https://netbird.example.com, then add users from the dashboard afterwards. External OIDC provider na optional, and you fit add am later with four values: name, client ID, client secret, and issuer. Guides wey tell you make you deploy Zitadel or Keycloak before NetBird dey describe setup wey no longer required. If you follow dem, you go need run one extra service.

Why all my peers dey show Connection type: Relayed?

Direct connections no dey form, so traffic dey pass through relay for your VPS. The usual cause na UDP 3478 wey dey blocked. Na the STUN port peers dey use to discover their own public address and port. Open am for VPS firewall and your provider separate network firewall. Then run netbird status --detail again and read the Direct: line. For network wey NAT dey assign different port for each destination, relayed na the only possible result. Nothing dey misconfigured.

My client connect, but dashboard no show any peers. Wetin happen?

The client register with NetBird hosted service instead of your server. This one dey happen when --management-url no dey included. netbird status --detail go print the server wey e dey talk to for the Management: line. Value like https://api.netbird.io:443 go confirm am. Run sudo netbird down, then sudo netbird up --management-url https://netbird.example.com, and the peer go show for your dashboard.

How self-hosted NetBird different from Headscale?

Both replace hosted control server with one wey you dey run. Headscale na control plane only. You manage am with the headscale command and config file. E no get official web console, and e dey drive the official Tailscale clients. NetBird ships its own client, admin dashboard, and identity provider integration for the same stack. Headscale smaller to run and e keep its state for files. NetBird easier to give people wey no go use terminal.

Wetin size VPS self-hosted NetBird server need?

The documented minimum na 1 CPU and 2 GB memory, and 2 GB na the number you suppose buy. The practical minimum don drop reach around 1 GB for recent releases because identity provider now dey embedded instead of separate deployment. Decline the optional proxy service during install. This one also skip the CrowdSec prompt. Stay with default SQLite store until you truly need PostgreSQL.