Skip to content

Latest commit

 

History

History
332 lines (251 loc) · 10.8 KB

File metadata and controls

332 lines (251 loc) · 10.8 KB

caddy-netbird

A Caddy plugin that embeds a NetBird client, allowing Caddy to proxy traffic through a NetBird network. Supports HTTP reverse proxying (Layer 7) and raw TCP/UDP proxying (Layer 4) via caddy-l4.

Prerequisites

This plugin only handles the Caddy side. It joins the NetBird network as a peer using the provided setup key and dials upstream addresses through the tunnel. All NetBird management configuration must be done beforehand:

  • The setup key must be created in the NetBird management dashboard.
  • Access control policies must allow traffic from the Caddy peer to the upstream peers/services.
  • Networks (routes) must be configured if the upstream is behind a routed network (e.g. 192.168.1.0/24).
  • The upstream peers must be online and reachable within the NetBird network.

This plugin does not create, modify, or manage any NetBird resources. It is a consumer of the network, not an administrator.

Build

Build from source (includes L4 support):

go build -o caddy ./cmd/caddy

xcaddy

NetBird uses forked dependencies that require replace directives. Since xcaddy does not propagate replace directives from plugin modules, you must pass them explicitly:

xcaddy build \
    --with github.com/lixmal/caddy-netbird \
    --replace github.com/cloudflare/circl=github.com/cunicu/circl@v0.0.0-20230801113412-fec58fc7b5f6 \
    --replace github.com/dexidp/dex=github.com/netbirdio/dex@v0.244.0 \
    --replace github.com/getlantern/systray=github.com/netbirdio/systray@v0.0.0-20231030152038-ef1ed2a27949 \
    --replace github.com/kardianos/service=github.com/netbirdio/service@v0.0.0-20240911161631-f62744f42502 \
    --replace github.com/libp2p/go-netroute=github.com/netbirdio/go-netroute@v0.0.0-20240611143515-f59b0e1d3944 \
    --replace github.com/pion/ice/v4=github.com/netbirdio/ice/v4@v4.0.0-20250908184934-6202be846b51 \
    --replace golang.zx2c4.com/wireguard=github.com/netbirdio/wireguard-go@v0.0.0-20260107100953-33b7c9d03db0

Container

podman run -d \
  --cap-add NET_BIND_SERVICE \
  -v /path/to/Caddyfile:/config/Caddyfile:ro \
  -p 80:80 -p 443:443 \
  ghcr.io/lixmal/caddy-netbird:latest

The image runs as a non-root user. NET_BIND_SERVICE is required to bind ports 80 and 443.

Caddyfile

See examples/ for complete Caddyfile configurations. Below is a quick overview.

Ingress (internet to NetBird)

Expose a NetBird peer to the public internet:

{
    netbird {
        management_url https://api.netbird.io:443
        setup_key {$NB_SETUP_KEY}

        node ingress {
            hostname caddy-ingress
        }
    }
}

app.example.com {
    reverse_proxy backend.netbird.cloud:8080 {
        transport netbird ingress
    }
}

Egress (NetBird to external)

Make external services available to NetBird peers. Uses netbird/<node>:<port> (TCP) or netbird-udp/<node>:<port> (UDP) listener address formats to bind on the NetBird virtual interface:

{
    netbird {
        management_url https://api.netbird.io:443
        setup_key {$NB_SETUP_KEY}

        node egress {
            hostname caddy-egress
            wireguard_port 0
            block_inbound false
        }
    }

    layer4 {
        # Forward TCP port to an external service
        netbird/egress:9080 {
            route {
                proxy external-host:8080
            }
        }
        # SOCKS5 proxy for dynamic destinations
        netbird/egress:1080 {
            route {
                socks5
            }
        }
        # Forward UDP to an internal DNS server
        netbird-udp/egress:5353 {
            route {
                proxy udp/dns-server.internal:53
            }
        }
    }
}

NetBird peers can then connect to <caddy-egress-ip>:9080 for the forwarded service, or use <caddy-egress-ip>:1080 as a SOCKS5 proxy.

The block_inbound false option is required for egress nodes: it allows incoming connections from NetBird peers to reach the listener.

Layer 4 (TCP/UDP)

Requires caddy-l4. The layer4 block goes inside the global options.

{
    layer4 {
        :2222 {
            route {
                netbird backend.netbird.cloud:22 ingress
            }
        }
    }
}

See examples/ for more L4 configurations (UDP, SNI routing, mixed HTTP+L4).

Admin API

The plugin registers endpoints on Caddy's admin API (default: localhost:2019) for debugging and runtime control.

Status

# Human-readable output (default)
curl localhost:2019/netbird/status

# JSON output
curl 'localhost:2019/netbird/status?format=json'

Example text output:

Node: ingress
  NetBird IP:  100.0.50.187/16
  FQDN:        caddy-ingress.netbird.cloud
  Management:  https://api.netbird.io:443  Connected
  Signal:      https://signal.netbird.io   Connected
  Relay:       rel://relay.netbird.io      Available

  Peers (3):
  FQDN                       IP          Status      Latency  Transfer       Conn     Handshake  Routes
  ----                       --          ------      -------  --------       ----     ---------  ------
  backend.netbird.cloud      100.0.1.10  Connected   1.2ms    1.5 KiB/2 KiB  P2P      13s ago    192.168.1.0/24
  api-backend.netbird.cloud  100.0.1.20  Connected   3.4ms    800 B/1.2 KiB  Relayed  45s ago    -
  web-backend.netbird.cloud  100.0.1.30  Connecting  -        -              -        -          -

Ping

Test connectivity to a host through the NetBird network:

# TCP ping (default)
curl -X POST localhost:2019/netbird/ping \
  -d '{"node": "ingress", "address": "backend.netbird.cloud:8080"}'

# ICMP ping
curl -X POST localhost:2019/netbird/ping \
  -d '{"node": "ingress", "address": "backend.netbird.cloud", "network": "ping"}'

# UDP ping
curl -X POST localhost:2019/netbird/ping \
  -d '{"node": "ingress", "address": "dns-server.netbird.cloud:53", "network": "udp"}'

Response:

{"reachable": true, "latency": 1234567}

The node field defaults to "default" if omitted. Latency is in nanoseconds.

Log level

Change the NetBird client log level at runtime:

curl -X PUT localhost:2019/netbird/log-level -d '{"level": "debug"}'

Valid levels: panic, fatal, error, warn, info, debug, trace.

Architecture

The plugin registers five Caddy modules:

Module Caddy ID Purpose
App netbird Manages NetBird client lifecycle, config, and usage pool
Transport http.reverse_proxy.transport.netbird Dials HTTP upstreams through the NetBird network
Handler layer4.handlers.netbird Proxies raw TCP/UDP through the NetBird network (requires caddy-l4)
Listener netbird (network) Binds listeners on the NetBird virtual interface for egress
Admin API admin.api.netbird Exposes status, ping, and log level endpoints on the admin API

The embedded NetBird client (embed.Client) runs entirely in userspace without requiring a TUN device or root privileges. Upstream traffic is dialed through the tunnel while Caddy handles TLS termination, load balancing, health checks, retries, and all other reverse proxy features.

Client sharing

Multiple sites can share the same NetBird client by referencing the same node name. Clients are ref-counted via caddy.UsagePool and survive config reloads without reconnecting.

Global options

Option Description
management_url Default management server URL
setup_key Default setup key for authentication
log_level NetBird client log level (default: info)

Node options

Option Description
management_url Override app-level management URL
setup_key Override app-level setup key
hostname Device name in the NetBird network (default: caddy-<node>)
pre_shared_key Pre-shared key for the network interface
wireguard_port Port for the network interface (default: 51820 via NetBird)
block_inbound Block inbound connections from peers (default: true). Set to false for egress nodes

Note on wireguard_port: For reliable peer-to-peer connectivity, the configured port (or the default random port) should be exposed via port forwarding on the host's firewall/NAT. Without it, connections may fall back to relayed traffic which adds latency.

Multiple nodes

Each node creates a separate NetBird peer identity. This is useful when connecting to different networks or management servers from a single Caddy instance.

Different management servers. No shared defaults needed, each node specifies its own management URL and setup key:

{
    netbird {
        node corp {
            management_url https://netbird.corp.example.com:443
            setup_key {$NB_CORP_KEY}
            hostname caddy-corp
            wireguard_port 0
        }

        node staging {
            management_url https://netbird.staging.example.com:443
            setup_key {$NB_STAGING_KEY}
            hostname caddy-staging
            wireguard_port 0
        }
    }
}

corp-app.example.com {
    reverse_proxy backend.corp.internal:8080 {
        transport netbird corp
    }
}

staging-app.example.com {
    reverse_proxy backend.staging.internal:8080 {
        transport netbird staging
    }
}

Same management, different setup keys. Useful for separate peer identities with different access policies on the same network:

{
    netbird {
        management_url https://api.netbird.io:443

        node web {
            setup_key {$NB_WEB_KEY}
            hostname caddy-web
            wireguard_port 0
        }

        node api {
            setup_key {$NB_API_KEY}
            hostname caddy-api
            wireguard_port 0
        }
    }
}

web.example.com {
    reverse_proxy web-backend.netbird.cloud:8080 {
        transport netbird web
    }
}

api.example.com {
    reverse_proxy api-backend.netbird.cloud:3000 {
        transport netbird api
    }
}

Note: Each node binds its own network interface port. When running multiple nodes, set distinct wireguard_port values to avoid conflicts.

Upstream TLS

The NetBird network encryption and upstream TLS are independent concerns. The upstream behind the tunnel may be a plain HTTP service on a peer, or it could be an HTTPS endpoint reached via a NetBird route to an external network.

TLS to the upstream is automatically enabled when using https:// upstream addresses. You can also configure it explicitly in the transport block:

Option Description
tls Enable TLS to upstream with default settings
tls_insecure_skip_verify Skip TLS certificate verification (testing only)
tls_server_name Override the server name for TLS verification