🔐 Remote Access (NetBird VPN)
Audience: System Administrators / DevOps Leads Purpose: Standard Operating Procedure for accessing internal, non-public infrastructure (the home router, host-only admin ports) from outside the office network.
Our main server sits behind a residential ISP connection. Residential routers periodically get swapped or reset by the ISP, which silently drops any manually configured port forward. When that happens, whoever needs remote access is locked out with no way back in until someone is physically on-site.
To remove this single point of failure, we run a self-hosted NetBird instance. NetBird is a fully open-source (BSD-3-Clause), WireGuard-based mesh VPN. Every device that needs access — an admin's laptop, this server itself — runs the NetBird client and joins a private overlay network. Connectivity no longer depends on any port being forwarded on the ISP router.
Why self-hosted, and why not Tailscale
Tailscale's client is open source, but its coordination server (the piece that lets two NATed devices find each other) is closed-source SaaS. NetBird's entire stack — coordination server included — is open source and something we run ourselves, consistent with our general self-hosting standard. See Server Topology for where this fits among our other self-hosted services.
1. Architecture
The self-hosted install runs as two containers, both bound to 127.0.0.1 only — never exposed directly:
| Container | Role |
|---|---|
netbird-dashboard |
Web UI, 127.0.0.1:8080 |
netbird-server |
Combined Management + Signal + Relay + STUN service, 127.0.0.1:8081 (HTTP/gRPC/WebSocket), plus 3478/udp for STUN |
Both containers are deployed via docker compose, with their config living at /opt/netbird/ (docker-compose.yml, config.yaml, dashboard.env) — per our config-location standard.
Public HTTPS traffic reaches the stack through our existing Caddy instance, which reverse-proxies to the two 127.0.0.1 ports. NetBird's install tooling ships a "generate a Caddyfile snippet" option specifically for this — it's wired into the main Caddyfile with a single import line, so it lives self-contained under /opt/netbird/ instead of being inlined into the shared Caddyfile:
import /opt/netbird/caddyfile-netbird.txt
No new inbound port required
STUN (3478/udp) enables optional direct peer-to-peer connections and does not need to be forwarded on the router — if it isn't reachable, NetBird falls back to relaying traffic over the same HTTPS port Caddy already serves, so the VPN keeps working even with zero additional ports opened.
2. Adding a New Peer
There are two distinct ways a device joins the network — use the right one for the job.
A headless machine (a server, this host)
Use a Setup Key — a one-off registration credential, generated per device:
- Dashboard → Peers → Add Peer → Setup Keys → Create Setup Key.
- Set the key type to one-off with a usage limit of
1. Never create a reusable key for a single device. - On the target machine:
curl -fsSL https://pkgs.netbird.io/install.sh | sudo bash sudo netbird up --management-url https://<netbird-domain> --setup-key <key> --hostname '<descriptive-name>'
Setup keys are bearer credentials
A setup key is not tied to a specific device — whoever holds the string can register a peer with it. Treat it exactly like a password: generate it immediately before use, paste it directly into the target machine's terminal, and never into a chat channel, ticket, or commit message. A one-off key with a usage limit of 1 is consumed on first use and cannot be replayed, which is the main practical safeguard available to us (see the note on Peer Approval below).
A personal device (a laptop)
Use interactive login instead — no setup key involved:
curl -fsSL https://pkgs.netbird.io/install.sh | sudo bash
netbird up --management-url https://<netbird-domain>
This prints a browser login URL. Log in with your own dashboard account. The device registers as a peer tied to your user.
3. Access Control Model
NetBird is zero-trust by default: a peer being on the network grants it no access by itself. Reachability is defined through three concepts, configured entirely from the dashboard:
- Networks — a named container mapping one environment (e.g. "Home LAN") to a routing peer and a set of resources.
- Resources — the actual destinations inside that environment. Prefer the narrowest type that does the job:
- Host (
/32, a single IP) — the default choice. Use this for "the router admin page" or "SSH into this host," not a whole subnet. - Subnet — a full CIDR range. Only use this if you genuinely need broad access to many devices in one environment; it's a much larger blast radius than a Host resource.
- Host (
- Policies — explicit grants connecting a source (a user/peer group) to a destination (a resource), restricted to specific protocols and ports. Nothing is reachable until a policy says so.
Disable the built-in \"Default\" policy
A fresh self-hosted install ships with a policy literally named Default, enabled, that permits all protocols and all ports, bidirectionally, between every peer in the account's "All" group. This silently overrides the narrow, per-resource policies you build — disable or delete it as one of the first things you do after setup. Confirm afterward that your intended per-resource policies (e.g. router admin UI on TCP 80/443 only) still work as expected.
4. Known Limitation: Peer Approval
Peer Approval — a setting that would hold every new peer in a "pending" state until an admin manually approves it — is gated behind NetBird's Enterprise license and is not available on this self-hosted community deployment.
The practical mitigation, given that gap: always issue one-off, single-use setup keys (see Section 2) rather than reusable ones, and generate them only immediately before the device that needs one is ready to run netbird up. A one-off key is consumed and dead after its first successful use, which closes the realistic replay window even without an approval gate. Also confirm Peer Login Expiration is enabled on the account (Dashboard → Settings) — this forces peers to periodically re-authenticate, an orthogonal protection that is available on the free tier.
5. Operational Checks
# Container health, from the server
docker ps --filter name=netbird
# Management/signal/relay server logs
docker logs netbird-server
# From any peer machine, connection status
netbird status
If a peer can't log in with a setup key and the client reports setup key is invalid, check the key's state in the dashboard first — a one-off key only ever works once; a second device (or a retried command that already succeeded) will always be rejected after that.