Self-host NetBird VPN Server for VPS, Step by Step
Run NetBird mesh VPN on one VPS with DNS, TLS, pinned quickstart script, setup keys for unattended peers, plus an honest Headscale comparison.
Wetin self-hosting the NetBird VPN server dey give you
Self-hosting the NetBird VPN server dey put the control plane for VPS wey you own: na the part wey hold 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 be WireGuard, with encryption direct between your machines. Wetin change be say no outside company dey hold your device inventory or your login flow.
NetBird dey between two things wey you fit don already know. E be mesh overlay, so peers connect to each other instead of sending everything through one gateway. E also fit self-host end to end, so e dey compete with Headscale, the self-hosted Tailscale control server. If na only single-gateway tunnel you don ever run, read the difference between plain WireGuard and a mesh overlay first, because na this 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 job need. A plain WireGuard VPN on a single VPS or a Tailscale exit node fit do am with much less thing to run.
Wetin the stack actually dey run
The layout change recently, and most older write-ups still describe the old one. As of August 2026, for release v0.76.2, the quickstart script dey write one Compose file with three services by default.
netbird-servercarry the management API, the signal service, the relay with one embedded STUN listener, and one embedded identity provider. For older releases, these ones dey run as separate containers, and the identity provider na separate Zitadel install wey you need build first.dashboardna the admin web console.traefikdey terminate TLS (transport layer security) and request certificate from Let's Encrypt for first start.
Two more services dey available, 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 dey use memory for small box.
If you dey come from wg-easy for one Docker container, this one get plenty more parts. Wetin e give you 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
Public domain name no be optional. The dashboard, API and relay all dey use HTTPS for port 443, and Traefik dey collect certificate from Let's Encrypt through HTTP challenge. This challenge need 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 for DNS to propagate before you run anything.
dig +short netbird.example.comThis one must print your server address. If you run the installer before DNS propagate, certificate request go fail for first start. Repeated failed validations fit reach Let's Encrypt rate limits, so you go wait one hour before you try again.
Three ports must dey reachable from internet: TCP 80 for certificate challenge and redirect to HTTPS, TCP 443 for dashboard, API, signal and relay traffic, 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 statusOpen dem for your provider network firewall too. For most VPS panels, this na separate control. Na this one dey make a box refuse connections even when its own ufw status look correct.
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 relay on TCP 443, so everything fit look normal. But you go get Connection type: Relayed for every peer, and all traffic go cross your VPS instead of going peer to peer.
For the software side, you need Docker with Compose v2 plugin, plus jq and curl. The script checks all of dem and stops if any one dey missing. If Docker still new for this box, first make Docker Compose dey work for the VPS.
Ports if you skip the bundled reverse proxy
If you run without Traefik, the individual services go expose directly, and the port list go longer:
- TCP 80, HTTP redirects
- TCP 443, HTTPS
- TCP 33073, management gRPC
- TCP 10000, signal gRPC
- TCP 33080, relay over WebSocket or QUIC
- UDP 3478, STUN
Choose this only if the box already dey terminate TLS for another service. Otherwise, bundled Traefik mean fewer rules and fewer mistakes.
Install NetBird server with the quickstart script
The documented one-liner dey pipe the newest release straight into shell:
curl -fsSL https://github.com/netbirdio/netbird/releases/latest/download/getting-started.sh | bashPin am instead. latest dey change, so if you run the same command two weeks apart, e go produce two different installs, and nothing for disk go record which one write your config. Download a 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.shThe script first ask for the domain:
Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com):Then e 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 5 dey write config snippet and leave the wiring for you. That one correct for machine wey already dey run proxy, but e wrong for fresh machine. Option 0 then ask for Let's Encrypt email address, wey dem go use for expiry notices.
For first install, answer no to NetBird Proxy service. E need two extra DNS records, proxy.netbird.example.com and wildcard *.proxy.netbird.example.com, and e no do anything for plain mesh. Answer no to CrowdSec too. You fit add both later.
The script dey write these files for current directory: docker-compose.yml, config.yaml with mode 600, dashboard.env, and traefik-dynamic.yaml if you choose bundled Traefik. 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-serverEvery service suppose read running, and server log suppose settle instead of restarting for loop. Monitor the certificate separately:
docker compose logs traefik | grep -i acmeACME (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 80 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 account go become the first admin, and the page go redirect you to the login form.
That account dey inside NetBird own user store. An identity provider wey dey inside the netbird-server container dey power am. Nothing external dey involved. This na the biggest change from the self-hosted NetBird of one year ago. That time, working install mean say you first stand up Zitadel or Keycloak, then copy four OIDC (OpenID Connect) values into setup.env before anything fit start.
If browser show certificate warning instead of setup page, the certificate no issue. Fix am before you continue. The dashboard dey talk to the API through the same hostname, and bad certificate fit make am 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 | shFor Debian and Ubuntu, that script dey configure NetBird's package repository first, then e install the client through apt. So na the package manager dey manage am either way. If piping script enter shell no dey okay for you, save am first with curl -fsSL -o install.sh https://pkgs.netbird.io/install.sh and read am before you run sh install.sh. Anyhow, confirm wetin install:
apt-cache policy netbirdnetbird 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.comIf you omit --management-url, the client go register with NetBird's hosted service because na the default wey dem compile inside. The command still go succeed, the machine still go get address, and your self-hosted dashboard go remain empty. Almost everybody dey make this mistake 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 wt0Read four lines from netbird status: Management: Connected, Signal: Connected, one Relays: line wey report every relay wey dey available, 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 anybody needing to touch am
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 interactive step. Create one for dashboard under Setup Keys.
Two kinds dey. One-off key dey authenticate exactly one machine, then e don spend. Reusable key fit register many machines, with optional limit for how many. Both get expiry time. Both fit automatically assign new peer to group, so access rules for that group go apply immediately machine show up.
sudo netbird up --setup-key <SETUP-KEY> \
--management-url https://netbird.example.com \
--hostname build-runner-01--hostname 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 go help anybody.
For containers and short-lived build agents, mark the key as ephemeral when you create am. Peers wey ephemeral key register go remove automatically after dem stay offline for more than 10 minutes. This one dey keep dead entries comot from peer list.
Understand this limit before you plan around setup keys: when key expire or you delete am, e go stop new registrations. E no go disconnect machines wey already register with am. To remove machine access, you need remove that peer.
You still need separate identity provider?
For small install, no. The built-in user store dey handle accounts wey you create from dashboard, and this one enough for small number of people.
You need external identity provider when you already get one and no want maintain second user list. NetBird accepts 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 appear for login page. Keep one local admin account with strong password. If OIDC configuration spoil, you still get way to enter.
NetBird or Headscale: which control plane you suppose run?
Both dey remove the same dependency: the hosted control server wey your clients for normally phone home to. But dem no be the same kind project.
Headscale dey reimplement the Tailscale control server, and you go continue to use the official Tailscale clients. No official web console dey. You go 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 fit people wey want make their state dey for files and make dem track changes with version control.
NetBird dey ship the complete product: e get im own client, im own dashboard, embedded identity provider, and access policies wey you fit edit for browser. This one mean more moving parts for your VPS, but e reduce the work to hand management 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 everything yourself.
How small 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 NetBird Proxy service, wey dey publish internal services on public hostnames and get nothing to do with peers connecting. Decline CrowdSec, wey make sense to add later for exposed box instead of day one. Keep the default SQLite store for the netbird_data volume, and move to PostgreSQL only when you split the deployment across machines or encounter real concurrency. Documentation describe this migration as something you fit do later.
The relay na the one component wey you no fit remove. Two peers wey their NAT assign different port for every destination no go ever establish direct tunnel, so relay na the only path wey make dem work at all. Disabling am save very little memory and break connections in a 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 be identical for the relay and the main server, or clients go fail to authenticate to am.
Wahala wey fit happen, 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 point to this VPS yet, or TCP 80 dey closed somewhere between Let's Encrypt and the container. Most times, na provider network firewall cause am, no be ufw. Fix the cause before you retry repeatedly, because failed validations get rate limit and you go lock yourself out from retrying for one hour.
Client talk say e connect, but dashboard empty. Client register with NetBird hosted service because --management-url no dey. Run netbird status --detail and read the Management: line. E go show the server wey client dey actually talk to. If you see Management: Connected to https://api.netbird.io:443, e mean say client connect to the 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 pass through your VPS and add one latency hop. Check UDP 3478 for the VPS firewall and provider firewall, because STUN na wetin help peer learn its own public address and port. netbird status --detail still print Direct: false and the ICE (interactive connectivity establishment) candidate types for each peer. This one show how far the connection attempt reach. For some networks, relayed na the only result wey fit work, and nothing dey wrong.
Peer join but e no fit reach anything. To dey inside the mesh no mean say two peers fit talk to each other. Access policies decide that, and group wey no get policy attached no fit reach anything. Check the policy for dashboard before you start debugging 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. If na anything wey you no fit identify, netbird debug bundle --anonymize --system-info go collect logs, status, routes, DNS settings and firewall state inside one archive.
Backups and upgrades
Two things dey carry the whole install: the directory wey hold docker-compose.yml and config.yaml, and the Docker volume wey hold the database and encryption keys. Back dem up together. config.yaml hold the key wey 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 -dCompose dey add project directory to volume names, so the volume wey documentation call netbird_data normally dey appear as netbird_netbird_data. Run docker volume ls first and use the name wey e print, or the docker run above go silently create empty volume and archive nothing. Keep the archives outside the VPS. If you already get backup tool, restic or BorgBackup fit handle the offsite part.
To upgrade the server, pull and recreate am:
docker compose pull
docker compose up -d
docker compose psBefore you depend on that, run docker compose config | grep image:. Any tag wey read latest suppose get pinned to a version. Na the same reason you pin the install script: you need know wetin dey run, and you need version wey you fit return to if upgrade misbehave. Clients dey upgrade through whichever package manager install dem.
FAQ
My need get my own identity provider to self-host NetBird?
No. Current releases get built-in user store, so you go create the first admin account for browser at https://netbird.example.com, then add users from 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, and if you follow dem, you go get one extra service to run.
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 block, and na 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, and nothing misconfigured.
My client connect, but dashboard no show peers. Wetin happen?
The client register with NetBird hosted service instead of your server. Na this dey happen when --management-url no dey set. netbird status --detail print the server wey e dey talk to for the Management: line, so value like https://api.netbird.io:443 confirm am. Run sudo netbird down, then sudo netbird up --management-url https://netbird.example.com, and the peer go appear for your dashboard.
How self-hosted NetBird different from Headscale?
Both replace hosted control server with one wey you run yourself. Headscale na control plane only: you manage am with headscale command and config file, no official web console dey, and e dey drive official Tailscale clients. NetBird ship with im own client, admin dashboard and identity provider integration for the same stack. Headscale smaller to run and e keep im state for files. NetBird easier to hand over to 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 wey you suppose buy. The practical floor don drop to around 1 GB for recent releases because identity provider don dey embedded instead of separate deployment. Decline the optional proxy and CrowdSec services during install, and remain with default SQLite store until you genuinely need PostgreSQL.