nblink

Updated

nblink forwards local ports into your NetBird network from an ordinary, unprivileged process. It runs the client in userspace mode, so there is no TUN device to create and no root to grant.

That makes it useful where the regular agent cannot run:

  • Managed laptops where you cannot install software that needs administrator rights.
  • Rootless containers, including OpenShift and Podman.
  • Short-lived environments such as CI jobs, where installing an agent is not worth it.

Names on the upstream side of a forward are resolved inside the tunnel, so /etc/resolv.conf is never modified.

Use cases

An MCP server or local AI agent reaching an internal API

AI agents increasingly run in containers or restricted sandboxes where installing a client is not an option, and the consumer is code rather than a person, so opening a browser is not a substitute.

nblink --forward 'http://3000=https://tickets.internal' --state-dir ~/.nblink

Point the MCP server at http://localhost:3000. The agent carries a real peer identity, so what it can reach is decided by your policies rather than by whatever the agent was told to do.

Agent Network access without installing a client

An Agent Network endpoint only answers when you are connected to your network and authenticated through your IdP, which normally means installing the client and therefore having root.

nblink --forward 'http://8080=https://mirror.netbird.ai'

Set the agent's base URL to http://localhost:8080. It still gets identity, spending limits and the models you approved, and no API key is handed to the agent itself.

A sidecar for an app you cannot modify

A vendor or legacy application hardcodes an HTTP endpoint and ignores proxy settings, so pointing it somewhere else is not possible. Running nblink beside it gives it localhost:8080 without touching the application, on platforms that forbid privileged containers and assign an arbitrary UID at run time.

Install

Download the archive for your platform from the releases page, extract it, and run the binary. There is no installer and no service to register.

tar -xzf nblink_<version>_linux_amd64.tar.gz
./nblink --help

Forward a port

A forward is a single argument, and the flag repeats:

nblink --forward 'http://8080=https://grafana.internal'

The scheme on the left selects the listener type. The right side is the upstream reached over your network. Giving only a port binds loopback, so the command above listens on 127.0.0.1:8080.

With no setup key, nblink opens your browser to log in. It always prints the login URL as well, so the same command works over SSH.

Forward several services from one session and one login:

nblink --forward 'http://8080=https://grafana.internal' \
       --forward 'http://9090=https://prometheus.internal'

Use 0 as the port to let the operating system pick a free one. The address actually bound is printed when the forward starts.

Check a configuration without connecting to anything:

nblink --forward 'http://8080=https://grafana.internal' --check

Run without a browser

Pass a setup key to log in without any interaction:

nblink --forward 'http://8080=https://grafana.internal' --setup-key "$NB_SETUP_KEY"

Run in a container

Every flag has a matching NB_-prefixed environment variable, which is how the image is configured. NB_FORWARD accepts a comma separated list.

docker run --rm -p 127.0.0.1:8080:8080 \
  --mount type=bind,src="$(pwd)/nb_setup_key",dst=/run/secrets/nb_setup_key,readonly \
  -e NB_SETUP_KEY=file:/run/secrets/nb_setup_key \
  -e NB_FORWARD='http://0.0.0.0:8080=https://grafana.internal' \
  -e NB_ALLOW_PUBLIC_BIND=true \
  netbirdio/nblink

The image needs no --privileged, no --cap-add NET_ADMIN, no --device /dev/net/tun and no host networking. It runs as a non-root user and works on platforms that assign an arbitrary UID at run time.

Inside a container the listener has to bind 0.0.0.0, because a published port arrives on the container's own interface rather than its loopback. That still requires NB_ALLOW_PUBLIC_BIND, so exposing a forward is always something you wrote down.

A forward on a public address accepts requests addressed to localhost or to an IP address. A name that other machines use to reach it, such as a DNS name or a Kubernetes Service name, has to be listed with --allowed-host (NB_ALLOWED_HOST); an unlisted name is answered with 421. See Access control for what listing a name trusts.

The image keeps its state in memory, so it runs on a read-only root filesystem and each start registers a new peer. To keep one identity across restarts, set NB_STATE_DIR and mount a writable volume there:

docker run --rm --read-only -p 127.0.0.1:8080:8080 \
  -v nblink-state:/var/lib/nblink -e NB_STATE_DIR=/var/lib/nblink \
  ...

Without a state directory, use an ephemeral setup key so the peers left behind by earlier starts are removed once they go offline.

A kept identity is not yet a kept login. Without a setup key every start runs the browser flow, even when the peer's session is still valid. Give a restart that has to be unattended a setup key.

Configuration

FlagEnvironment variableDefaultDescription
--forwardNB_FORWARDForward spec, repeatable. Comma separated in the environment.
--setup-keyNB_SETUP_KEYLog in without a browser. Accepts a file: prefix.
--management-urlNB_MANAGEMENT_URLNetBird cloudManagement server URL.
--hostnameNB_HOSTNAMEhost namePeer name in your network.
--state-dirNB_STATE_DIRmemoryDirectory for identity and state.
--log-levelNB_LOG_LEVELinfoLog level.
--allow-public-bindNB_ALLOW_PUBLIC_BINDfalseRequired to bind an address other than loopback.
--allowed-hostNB_ALLOWED_HOSTName a request may arrive under, beyond loopback names and addresses. Repeatable. Comma separated in the environment.
--no-browserNB_NO_BROWSERfalsePrint the login URL instead of opening a browser.
--checkNB_CHECKfalseValidate the configuration, print the forwards and exit.

Access control

A peer created by nblink is a peer like any other. It appears in your peer list and reaches only what your access control policies allow.

nblink only dials out. It refuses inbound connections and does not reach the network of the machine it runs on, so the peer cannot be used as a way into that machine.

A loopback forward answers only requests whose Host names the loopback interface or a name listed with --allowed-host, and refuses any request a browser marks as belonging to another page, through either a cross-origin Origin or a cross-site Sec-Fetch-Site. A web page you visit can point its own hostname at 127.0.0.1 or embed the address directly, and without those checks your browser could reach the upstream through the listener under your identity. Ordinary callers send neither header and are unaffected.

A browser too old to send Sec-Fetch-Site, which browsers have sent since 2020, can still reach a loopback forward with an embedded no-cors GET.

A forward on a public address applies the same checks and also accepts any IP address in Host, since a page cannot rebind an address it does not control.

A name listed with --allowed-host is trusted rather than merely permitted, and the list applies to every forward in the process, loopback ones included. Host is the one signal a rebound page cannot forge, and listing a name gives that up for it: a page served on that name can resolve it to the listener and send a matching Host and Origin. List only names you control, and prefer a fully qualified one, since a single-label name can be answered by anything on the local network.

Limits

Userspace mode carries TCP, UDP and ping only. An nblink peer cannot:

  • act as a routing peer or an exit node,
  • take over system DNS for the machine,
  • bind privileged ports below 1024.

If you need any of those, install the NetBird client instead.