[GH-ISSUE #4879] Feature Request: Tunnel-aware relay/transport mode to Netbird #9485

Open
opened 2026-08-05 01:22:19 -04:00 by saavagebueno · 1 comment
Owner

Originally created by @YourSandwich on GitHub (Nov 30, 2025).
Original GitHub issue: https://github.com/netbirdio/netbird/issues/4879

Summary

I want to run Netbird traffic through existing HTTPS/WebSocket tunnels and reverse-proxy style setups (for example via a trusted IP/hostname exposed by Tailscale Funnel or Cloudflare Tunnel). The idea is that Netbird’s tunnel packets should be relayable via a single entrypoint.

Right now I can approximate this using a complex combination of wstunnel, TProxy, nftables and custom routing. I’d like a much simpler, officially supported way to either:

  • Use a simple WG-style local UDP frontend that I can tunnel through tools like wstunnel / Tailscale Funnel / Cloudflare Tunnel, or
  • Allow native WireGuard clients to connect directly to netbird-manager, so Netbird can handle routing/peering while I reuse my existing WG-over-HTTPS/WebSocket setups.

Current working solution with WireGuard + wstunnel

For WireGuard I use wstunnel like this:

ExecStart=/usr/bin/wstunnel client \
  --http-headers-file /etc/wstunnel/headers \
  --connection-retry-max-backoff 120s \
  --websocket-ping-frequency 60s \
  --dns-resolver dns+https://1.1.1.1?sni=cloudflare-dns.com \
  -P v1/specialpath \
  -L udp://8080:targetvg_server:51822?timeout_sec=0 \
  -R socks5://127.0.0.1:8822 \
  wss://trustedserver:443

WireGuard config:

[connection]
id=SomeID
type=wireguard
autoconnect=false

[wireguard]
mtu=1200
private-key=xxx=

[wireguard-peer.xxx=]
endpoint=127.0.0.1:8080
preshared-key=xxxx=
preshared-key-flags=0
persistent-keepalive=20
allowed-ips=x.x.x.x/24;

[ipv4]
address1=x.x.x.x/32
gateway=x.x.x.x
method=manual

So WG just talks to 127.0.0.1:8080 over UDP; wstunnel then forwards that over WebSocket (wss://trustedserver:443).

Current workaround for Netbird + wstunnel TProxy

After a lot of pain I replicated this with Netbird using wstunnel’s TPROXY mode:

ExecStart=/usr/bin/wstunnel client \
  --http-headers-file /etc/wstunnel/headers \
  --connection-retry-max-backoff 5m \
  --websocket-ping-frequency 30s \
  --connection-min-idle 2 \
  --dns-resolver dns+https://1.1.1.1?sni=cloudflare-dns.com \
  -P v1/a9f0b1c2d3e4f5 \
  -L 'tproxy+tcp://15001' \
  -L 'tproxy+udp://15001?timeout_sec=3600' \
  wss://trustedserver:443

I run Netbird as a dedicated netbird user. Using nftables (firewalld frontend) I fwmark all traffic from that user and send it to a custom routing table.

/etc/firewalld/direct.xml:

<?xml version="1.0" encoding="utf-8"?>
<direct>
  <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="0">-o lo -j RETURN</rule>
  <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="1">-m owner --uid-owner wstunnel -j RETURN</rule>

  <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="2">-m owner --uid-owner netbird -d <netbird_iprange> -j RETURN</rule>
  <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="2">-m owner --uid-owner netbird -d 100.100.100.200/32 -j RETURN</rule>
  <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="2">-m owner --uid-owner netbird -d 169.254.169.254/32 -j RETURN</rule>
  <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="2">-m owner --uid-owner netbird -p udp --dport 53 -j RETURN</rule>
  <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="2">-m owner --uid-owner netbird -p tcp --dport 53 -j RETURN</rule>

  <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="3">-m owner --uid-owner netbird -j MARK --set-mark 0x66</rule>

  <rule ipv="ipv4" table="mangle" chain="PREROUTING" priority="0">-d <network_ip_relay> -m mark --mark 0x66 -j RETURN</rule>
  <rule ipv="ipv4" table="mangle" chain="PREROUTING" priority="0">-d 100.100.100.200/32 -m mark --mark 0x66 -j RETURN</rule>
  <rule ipv="ipv4" table="mangle" chain="PREROUTING" priority="0">-d 169.254.169.254/32 -m mark --mark 0x66 -j RETURN</rule>

  <rule ipv="ipv4" table="mangle" chain="PREROUTING" priority="1">-p udp -m mark --mark 0x66 -j TPROXY --on-port 15001 --on-ip 127.0.0.1</rule>
  <rule ipv="ipv4" table="mangle" chain="PREROUTING" priority="2">-p tcp -m mark --mark 0x66 -j TPROXY --on-port 15001 --on-ip 127.0.0.1</rule>

  <rule ipv="ipv4" table="nat" chain="POSTROUTING" priority="0">-s <network_ip_relay> -o ens192 -j MASQUERADE</rule>
  <rule ipv="ipv4" table="nat" chain="OUTPUT" priority="0">-m owner --uid-owner netbird -p udp --dport 53 -j DNAT --to-destination 1.1.1.1:53</rule>
  <rule ipv="ipv4" table="nat" chain="OUTPUT" priority="1">-m owner --uid-owner netbird -p tcp --dport 53 -j DNAT --to-destination 1.1.1.1:53</rule>
</direct>

/etc/systemd/system/wstunnel-tproxy-routing.service:

[Unit]
Description=policy routing for netbird→wstunnel
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/bin/ip rule add pref 98 fwmark 0x66 lookup 101
ExecStart=/usr/bin/ip route replace local 0.0.0.0/0 dev lo table 101
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

This works but is very complex, interferes with Netbird’s own routing table, and is fragile.

Problems with the current workaround

Issues I see with this setup:

  • After ~6h (key exchange) Netbird loses its TProxy port mapping and fails to reconnect → needs service restart.
  • P2P not working because of ICE, even with UDP TProxy set.
  • TURN proxy not working (only rels:// works).
  • Every internal IP that should be relayed has to be added to nftables manually.

Proposed solutions

1. Minimal requirement: simple “WG-style” UDP frontend

I don’t need a full, deeply integrated WebSocket transport in the Netbird client.

A minimal but very useful feature would be:

  • Let Netbird send its tunnel traffic to a single local UDP endpoint, similar to how WG works with wstunnel:

    -L udp://8080:targetvg_server:51822?timeout_sec=0 \
    

Concretely:

  • Allow configuring the Netbird client so that all WG-like traffic destined to the relay/manager goes to e.g. 127.0.0.1:8080.

  • A local wstunnel instance then listens on udp://127.0.0.1:8080 and forwards that over wss://trustedserver:443 to the real Netbird/relay endpoint.

  • From Netbird’s point of view this is just a normal UDP endpoint; all WebSocket/HTTPS magic is handled by wstunnel.

  • This would eliminate:

    • TProxy,
    • fwmarks,
    • extra routing tables,
    • and most of the nftables complexity on the client.

2. Alternative: allow native WireGuard clients to connect to netbird-manager

A different simple approach that would also solve my use case:

  • Allow native WireGuard clients (standard wg/wg-quick) to connect directly to netbird-manager as first-class peers.

  • netbird-manager would:

    • Issue proper WG configs for those peers.
    • Make sure Netbird clients treat those WG peers as regular Netbird peers and route traffic accordingly.

That how I could keep using the well-tested wg + wstunnel combo on restricted networks, while still leveraging Netbird’s:

  • control plane,
  • ACLs,
  • mesh logic.

3. Nice to have: tunnel-aware manager / obfuscation

Not strictly required, but would improve tunnel setups further:

  • Make netbird-manager tunnel-aware:

    • When a peer is known to be behind a tunnel/WebSocket, prefer serving a local tunnel/relay address instead of public relay information.
    • Avoid handing out unreachable public relay IPs to peers that can only reach each other via the tunnel.

Optionally, provide a native obfuscation / pluggable transport mode that:

  • Wraps Netbird traffic in something HTTP(S)/WebSocket-like.
  • Can be forwarded via generic tunnel providers such as Tailscale Funnel, Cloudflare Tunnel, etc.
  • Eliminates the need for the TProxy + nftables hacking I’m doing now.

Closing

In short, I’d already be very happy with one of these minimal options:

  • Netbird being able to use a simple local UDP frontend (WG-style) that I can tunnel with wstunnel, or
  • Allowing native WireGuard clients to connect to netbird-manager.

Everything else (tunnel-awareness / obfuscation) would be a big bonus, but these two would already make Netbird much easier and more robust to deploy on locked-down networks that require WebSocket/HTTPS tunneling.

Disclaimer: this issue (structure/formatting) was prepared with the help of an LLM.

Originally created by @YourSandwich on GitHub (Nov 30, 2025). Original GitHub issue: https://github.com/netbirdio/netbird/issues/4879 ## Summary I want to run Netbird traffic through existing HTTPS/WebSocket tunnels and reverse-proxy style setups (for example via a trusted IP/hostname exposed by Tailscale Funnel or Cloudflare Tunnel). The idea is that Netbird’s tunnel packets should be relayable via a single entrypoint. Right now I can approximate this using a complex combination of wstunnel, TProxy, nftables and custom routing. I’d like a much simpler, officially supported way to either: * Use a **simple WG-style local UDP frontend** that I can tunnel through tools like wstunnel / Tailscale Funnel / Cloudflare Tunnel, **or** * Allow **native WireGuard clients** to connect directly to `netbird-manager`, so Netbird can handle routing/peering while I reuse my existing WG-over-HTTPS/WebSocket setups. ## Current working solution with WireGuard + wstunnel For WireGuard I use wstunnel like this: ```ini ExecStart=/usr/bin/wstunnel client \ --http-headers-file /etc/wstunnel/headers \ --connection-retry-max-backoff 120s \ --websocket-ping-frequency 60s \ --dns-resolver dns+https://1.1.1.1?sni=cloudflare-dns.com \ -P v1/specialpath \ -L udp://8080:targetvg_server:51822?timeout_sec=0 \ -R socks5://127.0.0.1:8822 \ wss://trustedserver:443 ``` WireGuard config: ```ini [connection] id=SomeID type=wireguard autoconnect=false [wireguard] mtu=1200 private-key=xxx= [wireguard-peer.xxx=] endpoint=127.0.0.1:8080 preshared-key=xxxx= preshared-key-flags=0 persistent-keepalive=20 allowed-ips=x.x.x.x/24; [ipv4] address1=x.x.x.x/32 gateway=x.x.x.x method=manual ``` So WG just talks to `127.0.0.1:8080` over UDP; wstunnel then forwards that over WebSocket (`wss://trustedserver:443`). ## Current workaround for Netbird + wstunnel TProxy After a lot of pain I replicated this with Netbird using wstunnel’s **TPROXY** mode: ```ini ExecStart=/usr/bin/wstunnel client \ --http-headers-file /etc/wstunnel/headers \ --connection-retry-max-backoff 5m \ --websocket-ping-frequency 30s \ --connection-min-idle 2 \ --dns-resolver dns+https://1.1.1.1?sni=cloudflare-dns.com \ -P v1/a9f0b1c2d3e4f5 \ -L 'tproxy+tcp://15001' \ -L 'tproxy+udp://15001?timeout_sec=3600' \ wss://trustedserver:443 ``` I run Netbird as a dedicated `netbird` user. Using nftables (firewalld frontend) I fwmark all traffic from that user and send it to a custom routing table. `/etc/firewalld/direct.xml`: ```xml <?xml version="1.0" encoding="utf-8"?> <direct> <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="0">-o lo -j RETURN</rule> <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="1">-m owner --uid-owner wstunnel -j RETURN</rule> <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="2">-m owner --uid-owner netbird -d <netbird_iprange> -j RETURN</rule> <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="2">-m owner --uid-owner netbird -d 100.100.100.200/32 -j RETURN</rule> <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="2">-m owner --uid-owner netbird -d 169.254.169.254/32 -j RETURN</rule> <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="2">-m owner --uid-owner netbird -p udp --dport 53 -j RETURN</rule> <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="2">-m owner --uid-owner netbird -p tcp --dport 53 -j RETURN</rule> <rule ipv="ipv4" table="mangle" chain="OUTPUT" priority="3">-m owner --uid-owner netbird -j MARK --set-mark 0x66</rule> <rule ipv="ipv4" table="mangle" chain="PREROUTING" priority="0">-d <network_ip_relay> -m mark --mark 0x66 -j RETURN</rule> <rule ipv="ipv4" table="mangle" chain="PREROUTING" priority="0">-d 100.100.100.200/32 -m mark --mark 0x66 -j RETURN</rule> <rule ipv="ipv4" table="mangle" chain="PREROUTING" priority="0">-d 169.254.169.254/32 -m mark --mark 0x66 -j RETURN</rule> <rule ipv="ipv4" table="mangle" chain="PREROUTING" priority="1">-p udp -m mark --mark 0x66 -j TPROXY --on-port 15001 --on-ip 127.0.0.1</rule> <rule ipv="ipv4" table="mangle" chain="PREROUTING" priority="2">-p tcp -m mark --mark 0x66 -j TPROXY --on-port 15001 --on-ip 127.0.0.1</rule> <rule ipv="ipv4" table="nat" chain="POSTROUTING" priority="0">-s <network_ip_relay> -o ens192 -j MASQUERADE</rule> <rule ipv="ipv4" table="nat" chain="OUTPUT" priority="0">-m owner --uid-owner netbird -p udp --dport 53 -j DNAT --to-destination 1.1.1.1:53</rule> <rule ipv="ipv4" table="nat" chain="OUTPUT" priority="1">-m owner --uid-owner netbird -p tcp --dport 53 -j DNAT --to-destination 1.1.1.1:53</rule> </direct> ``` `/etc/systemd/system/wstunnel-tproxy-routing.service`: ```ini [Unit] Description=policy routing for netbird→wstunnel After=network.target [Service] Type=oneshot ExecStart=/usr/bin/ip rule add pref 98 fwmark 0x66 lookup 101 ExecStart=/usr/bin/ip route replace local 0.0.0.0/0 dev lo table 101 RemainAfterExit=yes [Install] WantedBy=multi-user.target ``` This works but is **very complex**, interferes with Netbird’s own routing table, and is **fragile**. ## Problems with the current workaround Issues I see with this setup: * After ~6h (key exchange) Netbird loses its TProxy port mapping and fails to reconnect → needs service restart. * P2P not working because of ICE, even with UDP TProxy set. * TURN proxy not working (**only `rels://` works**). * Every internal IP that should be relayed has to be added to nftables manually. ## Proposed solutions ### 1. Minimal requirement: simple “WG-style” UDP frontend I **don’t** need a full, deeply integrated WebSocket transport in the Netbird client. A minimal but very useful feature would be: * Let Netbird send its **tunnel traffic** to a **single local UDP endpoint**, similar to how WG works with wstunnel: ```ini -L udp://8080:targetvg_server:51822?timeout_sec=0 \ ``` Concretely: * Allow configuring the Netbird client so that all WG-like traffic destined to the relay/manager goes to e.g. `127.0.0.1:8080`. * A local wstunnel instance then listens on `udp://127.0.0.1:8080` and forwards that over `wss://trustedserver:443` to the real Netbird/relay endpoint. * From Netbird’s point of view this is just a normal UDP endpoint; all WebSocket/HTTPS magic is handled by wstunnel. * This would eliminate: * TProxy, * fwmarks, * extra routing tables, * and most of the nftables complexity on the client. ### 2. Alternative: allow native WireGuard clients to connect to `netbird-manager` A different simple approach that would also solve my use case: * Allow **native WireGuard clients** (standard `wg`/`wg-quick`) to connect directly to `netbird-manager` as first-class peers. * `netbird-manager` would: * Issue proper WG configs for those peers. * Make sure Netbird clients treat those WG peers as regular Netbird peers and route traffic accordingly. That how I could keep using the well-tested `wg + wstunnel` combo on restricted networks, while still leveraging Netbird’s: * control plane, * ACLs, * mesh logic. ### 3. Nice to have: tunnel-aware manager / obfuscation Not strictly required, but would improve tunnel setups further: * Make `netbird-manager` **tunnel-aware**: * When a peer is known to be behind a tunnel/WebSocket, prefer serving a **local tunnel/relay address** instead of public relay information. * Avoid handing out unreachable public relay IPs to peers that can only reach each other via the tunnel. Optionally, provide a **native obfuscation / pluggable transport** mode that: * Wraps Netbird traffic in something HTTP(S)/WebSocket-like. * Can be forwarded via generic tunnel providers such as **Tailscale Funnel**, **Cloudflare Tunnel**, etc. * Eliminates the need for the TProxy + nftables hacking I’m doing now. ## Closing In short, I’d already be very happy with one of these minimal options: * Netbird being able to use a **simple local UDP frontend** (WG-style) that I can tunnel with wstunnel, **or** * Allowing **native WireGuard clients** to connect to `netbird-manager`. Everything else (tunnel-awareness / obfuscation) would be a big bonus, but these two would already make Netbird much easier and more robust to deploy on locked-down networks that require WebSocket/HTTPS tunneling. **Disclaimer: this issue (structure/formatting) was prepared with the help of an LLM.**
saavagebueno added the feature-request label 2026-08-05 01:22:19 -04:00
Author
Owner

@RainyPixel commented on GitHub (Jan 14, 2026):

+1 same problem

<!-- gh-comment-id:3752865405 --> @RainyPixel commented on GitHub (Jan 14, 2026): +1 same problem
Sign in to join this conversation.
No Label feature-request
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#9485