[GH-ISSUE #1964] [SELF-HOSTED] Wrong public IP for client on a VPS #3878

Open
opened 2026-08-05 00:54:47 -04:00 by saavagebueno · 21 comments
Owner

Originally created by @kalwadi on GitHub (May 11, 2024).
Original GitHub issue: https://github.com/netbirdio/netbird/issues/1964

Describe the problem

NetBird Client (Debian 12 server hosted on Hetzner) shows wrong IP as Public IP. 172.21.0.1 is shown as Public IP
Wrong IP is sometimes shown with docker client as well

To Reproduce

Steps to reproduce the behavior:

  1. Create VPS on Hetzner (debian 12)
  2. Install native netbird client
  3. Join network
  4. Assigned Public IP is 172.21.0.1

Expected behavior

Actual Public IP is shown

Are you using NetBird Cloud?

No, Self-host NetBird

NetBird version

0.27.5

Additional context
https://github.com/netbirdio/netbird/issues/1728#issuecomment-2105639940

My controller is running on Hetzner VPS (arm CPU)
DNS record for subdomain is directly pointed to this IP
Authentik is used for authentication

Running NetBird client on Debian 12 VM (in Proxmox) at home shows correct public IP

On a clean VPS (no docker installed) I have tried running native client and wrong IP was showing

After that I installed docker and created container with this docker-compose.yml file, correct IP was showing in dashboard

volumes:
 netbird-client:

services:
  netbird:
    image: netbirdio/netbird:latest
    cap_add:
      - NET_ADMIN
      - SYS_ADMIN
      - SYS_RESOURCE
    environment:
      - NB_SETUP_KEY=xxxx
      - NB_MANAGEMENT_URL=https://netbird.my-domain.tld:33073
    volumes:
      - netbird-client:/etc/netbird
    restart: always

Some findings after a bit of testing:

  • New VPS (tried in 3 different Hetzner locations) 2x ARM CPU, 1x x86 CPU

    • Native client shows 172.21.0.1 as public IP
    • docker client with above shown docker-compose.yml shows correct IP
  • VPS where I run my NetBird controller shows wrong IP with native client and docker client

    • only thing running on this VPS is the controller (and broken netbird client)
    • this VPS was created for this and no other things were setup on this before
Originally created by @kalwadi on GitHub (May 11, 2024). Original GitHub issue: https://github.com/netbirdio/netbird/issues/1964 **Describe the problem** NetBird Client (Debian 12 server hosted on Hetzner) shows wrong IP as Public IP. `172.21.0.1` is shown as Public IP Wrong IP is sometimes shown with docker client as well **To Reproduce** Steps to reproduce the behavior: 1. Create VPS on Hetzner (debian 12) 2. Install native netbird client 3. Join network 4. Assigned Public IP is 172.21.0.1 **Expected behavior** Actual Public IP is shown **Are you using NetBird Cloud?** No, Self-host NetBird **NetBird version** `0.27.5` **Additional context** https://github.com/netbirdio/netbird/issues/1728#issuecomment-2105639940 My controller is running on Hetzner VPS (arm CPU) DNS record for subdomain is directly pointed to this IP Authentik is used for authentication Running NetBird client on Debian 12 VM (in Proxmox) at home shows correct public IP On a clean VPS (no docker installed) I have tried running native client and wrong IP was showing After that I installed docker and created container with this `docker-compose.yml` file, correct IP was showing in dashboard ```yml volumes: netbird-client: services: netbird: image: netbirdio/netbird:latest cap_add: - NET_ADMIN - SYS_ADMIN - SYS_RESOURCE environment: - NB_SETUP_KEY=xxxx - NB_MANAGEMENT_URL=https://netbird.my-domain.tld:33073 volumes: - netbird-client:/etc/netbird restart: always ``` Some findings after a bit of testing: - New VPS (tried in 3 different Hetzner locations) 2x ARM CPU, 1x x86 CPU - Native client shows `172.21.0.1` as public IP - docker client with above shown `docker-compose.yml` shows correct IP - VPS where I run my NetBird controller shows wrong IP with native client and docker client - only thing running on this VPS is the controller (and broken netbird client) - this VPS was created for this and no other things were setup on this before
saavagebueno added the self-hosting label 2026-08-05 00:54:47 -04:00
Author
Owner

@nuterum commented on GitHub (May 12, 2024):

To make sure we speak of the same thing i will describe what i understand and explain what happen.

If i understand correctly you have install netbird client on the same server as netbird server (self-hosted).
In my case it what i have done.

Then in this case you find a strange IP (in fact local IP) in your netbird peer.

It is not really a bug in itself but a problem with the understanding of public IP.
The "Public IP" show in the peer is the IP that is use to join the server in the IP header when connecting.

Normally you will pass by internet that why the "Public IP" is correct but in case of local machine or even local network because you can use netbird in local network too then the IP on the IP header will be a local one and will not have been change by a NAT.

That why in my case my server having netbird client show a public IP of "172.19.0.1".
The only downside is that without going to change the "location_country_code text,location_city_name text,location_geo_name_id integer" manually in the sqlite db then my peer will be unknow for region.

The only thing not test for now is another netbird client on another VPS of the same provider.

To test further i may need the configuration of your netbird server because depending on how its configure and how the provider handle VPS maybe the VPS exchange packet without going on internet but by local network of the provider (even if in different location it can be connect together by virtual network) and in this case it will depend of the configuration of your server but i guess your self-hosted netbird is a docker using the network 172.21.0.0/16 and the server have an interface "br-XXXXX" with ip "172.21.0.1/16" and local network NAT the source IP to 172.21.0.1.

You can at least check what ip is resolve for "netbird.my-domain.tld" on your different VPS with or without docker before posting your configuration.

If my guess is right docker may not use the DNS of the provider and find the public IP of "netbird.my-domain.tld" then pass by internet and without docker we use the provider DNS that answer a local IP for "netbird.my-domain.tld".

But to realize this test i need your server configuration and to know if you domain is register on Hetzner or not to be able to reproduce the same environement.

<!-- gh-comment-id:2106243321 --> @nuterum commented on GitHub (May 12, 2024): To make sure we speak of the same thing i will describe what i understand and explain what happen. If i understand correctly you have install netbird client on the same server as netbird server (self-hosted). In my case it what i have done. Then in this case you find a strange IP (in fact local IP) in your netbird peer. It is not really a bug in itself but a problem with the understanding of public IP. The "Public IP" show in the peer is the IP that is use to join the server in the IP header when connecting. Normally you will pass by internet that why the "Public IP" is correct but in case of local machine or even local network because you can use netbird in local network too then the IP on the IP header will be a local one and will not have been change by a NAT. That why in my case my server having netbird client show a public IP of "172.19.0.1". The only downside is that without going to change the "`location_country_code` text,`location_city_name` text,`location_geo_name_id` integer" manually in the sqlite db then my peer will be unknow for region. The only thing not test for now is another netbird client on another VPS of the same provider. To test further i may need the configuration of your netbird server because depending on how its configure and how the provider handle VPS maybe the VPS exchange packet without going on internet but by local network of the provider (even if in different location it can be connect together by virtual network) and in this case it will depend of the configuration of your server but i guess your self-hosted netbird is a docker using the network 172.21.0.0/16 and the server have an interface "br-XXXXX" with ip "172.21.0.1/16" and local network NAT the source IP to 172.21.0.1. You can at least check what ip is resolve for "netbird.my-domain.tld" on your different VPS with or without docker before posting your configuration. If my guess is right docker may not use the DNS of the provider and find the public IP of "netbird.my-domain.tld" then pass by internet and without docker we use the provider DNS that answer a local IP for "netbird.my-domain.tld". But to realize this test i need your server configuration and to know if you domain is register on Hetzner or not to be able to reproduce the same environement.
Author
Owner

@xrehpicx commented on GitHub (May 22, 2024):

Is there any way i can manually set the peer public IP, or whatever ip i want the peer to advertise as it's public IP

<!-- gh-comment-id:2126075853 --> @xrehpicx commented on GitHub (May 22, 2024): Is there any way i can manually set the peer public IP, or whatever ip i want the peer to advertise as it's public IP
Author
Owner

@remogeissbuehler commented on GitHub (May 25, 2024):

I had a smilar issue where some of my clients showed up as having the docker gateway IP (172.17.0.1 in this example) as their public IP (cf. #1728). I looked at the Caddy logs and saw that the X-Forwarded-For Headers were also set to this IP (172.17.0.1).

I checked the upstream caddy issues and this appears to occur when clients connect via IPv6 (see this issue: https://github.com/caddyserver/caddy/issues/4339). This happens since docker containers by default don't communicate over IPv6, so the docker gateway "NATs" these connections which then have the gateway address as a source.

I added IPv6 to the docker setup, by adding the following to /etc/docker/daemon.json

{
	"experimental": true,
	"ip6tables": true,
	"default-address-pools": [
		{ "base": "172.17.0.0/16", "size": 16 },
		{ "base": "172.18.0.0/16", "size": 16 },
		{ "base": "172.19.0.0/16", "size": 16 },
		{ "base": "172.20.0.0/14", "size": 16 },
		{ "base": "172.24.0.0/14", "size": 16 },
		{ "base": "172.28.0.0/14", "size": 16 },
		{ "base": "fd42:1234:5678::/48", "size": 64 }
	]
}

Note that adding address pools isn't strictly necessary, you can also set them on container creation (i.e. in the docker-compose file in this scenario). I used an IPv6 ULA, but I'm sure there's other / better ways ;)

We also need to enable IPv6 networking in the docker compose file:

networks:
  netbird:
    enable_ipv6: true

With those changes, the peers show up correctly with their IPv6 listed as the public IP.

<!-- gh-comment-id:2131459167 --> @remogeissbuehler commented on GitHub (May 25, 2024): I had a smilar issue where some of my clients showed up as having the docker gateway IP (172.17.0.1 in this example) as their public IP (cf. #1728). I looked at the Caddy logs and saw that the X-Forwarded-For Headers were also set to this IP (172.17.0.1). I checked the upstream caddy issues and this appears to occur when clients connect via IPv6 (see this issue: https://github.com/caddyserver/caddy/issues/4339). This happens since docker containers by default don't communicate over IPv6, so the docker gateway "NATs" these connections which then have the gateway address as a source. I added IPv6 to the docker setup, by adding the following to `/etc/docker/daemon.json` ``` { "experimental": true, "ip6tables": true, "default-address-pools": [ { "base": "172.17.0.0/16", "size": 16 }, { "base": "172.18.0.0/16", "size": 16 }, { "base": "172.19.0.0/16", "size": 16 }, { "base": "172.20.0.0/14", "size": 16 }, { "base": "172.24.0.0/14", "size": 16 }, { "base": "172.28.0.0/14", "size": 16 }, { "base": "fd42:1234:5678::/48", "size": 64 } ] } ``` Note that adding address pools isn't strictly necessary, you can also set them on container creation (i.e. in the docker-compose file in this scenario). I used an IPv6 [ULA](https://en.wikipedia.org/wiki/Unique_local_address), but I'm sure there's other / better ways ;) We also need to enable IPv6 networking in the docker compose file: ``` networks: netbird: enable_ipv6: true ``` With those changes, the peers show up correctly with their IPv6 listed as the public IP.
Author
Owner

@cipherw0lf commented on GitHub (Aug 9, 2024):

I'm having the same issue.
I used a Ubuntu 22.04 VPS and simply ran (as root) the getting-started-with-zitadel.sh script as per the self-host quickstart guide.

Relevant info:

  • Im not using IPv6
  • Im not running any clients on the server
  • ISSUE: All Public WAN addresses show the docker IP address of Caddy container instead of actual WAN address of the clients
  • I do believe the WAN address was showing correctly immediately after deployment (running script)
  • I attempted to update the server recently when v0.28.7 dropped
  • Issued commands as root:
    • docker compose down
    • docker compose pull
    • docker compose up -d
  • After this i believe issue started occurring
  • No other options were changed
  • All firewalls off

Note: the VPN is still working and connectivity doesn't seem to be affected as this point.

Caddy log snippet:

DBG ts=1723207925.2943292 logger=http.handlers.reverse_proxy msg=upstream roundtrip upstream=management:80 duration=0.072324231 request={"remote_ip":"172.18.0.1","remote_port":"33864","client_ip":"172.18.0.1","proto":"HTTP/2.0","method":"GET","host":"netbird.MYDOMAIN.net","uri":"/api/routes","headers":{"Sec-Gpc":["1"],"Authorization":["REDACTED"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["netbird.MYDOMAIN.net"],"X-Forwarded-For":["172.18.0.1"],"Cookie":["REDACTED"],"Sec-Ch-Ua":["\"Not)A;Brand\";v=\"99\", \"Google Chrome\";v=\"127\", \"Chromium\";v=\"127\""],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"],"Dnt":["1"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Sec-Ch-Ua-Mobile":["?0"],"Content-Type":["application/json"],"X-Forwarded-Proto":["https"],"Priority":["u=1, i"],"Accept-Language":["en-US,en-ZA;q=0.9,en;q=0.8,th;q=0.7"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Dest":["empty"],"Referer":["https://netbird.MYDOMAIN.net/peers"],"Accept":["application/json"],"Sec-Fetch-Site":["same-origin"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"netbird.MYDOMAIN.net"}} headers={"Vary":["Origin"],"Date":["Fri, 09 Aug 2024 12:52:05 GMT"],"Content-Length":["1425"],"Content-Type":["application/json; charset=UTF-8"]} status=200
<!-- gh-comment-id:2277882233 --> @cipherw0lf commented on GitHub (Aug 9, 2024): I'm having the same issue. I used a Ubuntu 22.04 VPS and simply ran (as root) the getting-started-with-zitadel.sh script as per the self-host quickstart guide. ## Relevant info: - Im not using IPv6 - Im not running any clients on the server - ISSUE: All Public WAN addresses show the docker IP address of Caddy container instead of actual WAN address of the clients - I do believe the WAN address was showing correctly immediately after deployment (running script) - I attempted to update the server recently when v0.28.7 dropped - Issued commands as root: - `docker compose down` - `docker compose pull` - `docker compose up -d` - After this i believe issue started occurring - No other options were changed - All firewalls off Note: the VPN is still working and connectivity doesn't seem to be affected as this point. Caddy log snippet: ``` DBG ts=1723207925.2943292 logger=http.handlers.reverse_proxy msg=upstream roundtrip upstream=management:80 duration=0.072324231 request={"remote_ip":"172.18.0.1","remote_port":"33864","client_ip":"172.18.0.1","proto":"HTTP/2.0","method":"GET","host":"netbird.MYDOMAIN.net","uri":"/api/routes","headers":{"Sec-Gpc":["1"],"Authorization":["REDACTED"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["netbird.MYDOMAIN.net"],"X-Forwarded-For":["172.18.0.1"],"Cookie":["REDACTED"],"Sec-Ch-Ua":["\"Not)A;Brand\";v=\"99\", \"Google Chrome\";v=\"127\", \"Chromium\";v=\"127\""],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"],"Dnt":["1"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Sec-Ch-Ua-Mobile":["?0"],"Content-Type":["application/json"],"X-Forwarded-Proto":["https"],"Priority":["u=1, i"],"Accept-Language":["en-US,en-ZA;q=0.9,en;q=0.8,th;q=0.7"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Dest":["empty"],"Referer":["https://netbird.MYDOMAIN.net/peers"],"Accept":["application/json"],"Sec-Fetch-Site":["same-origin"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"netbird.MYDOMAIN.net"}} headers={"Vary":["Origin"],"Date":["Fri, 09 Aug 2024 12:52:05 GMT"],"Content-Length":["1425"],"Content-Type":["application/json; charset=UTF-8"]} status=200 ```
Author
Owner

@nazarewk commented on GitHub (Apr 23, 2025):

Do you still have issues with latest/relatively recent NetBird versions?

<!-- gh-comment-id:2825221315 --> @nazarewk commented on GitHub (Apr 23, 2025): Do you still have issues with latest/relatively recent NetBird versions?
Author
Owner

@kalwadi commented on GitHub (Apr 23, 2025):

I don't have issues with other clients anymore, everything shows up correctly. (0.41.3)

Only client running on Netbird Server shows with local IP as PublicIP, but that is expected.

Image

<!-- gh-comment-id:2825302351 --> @kalwadi commented on GitHub (Apr 23, 2025): I don't have issues with other clients anymore, everything shows up correctly. (0.41.3) Only client running on Netbird Server shows with local IP as PublicIP, but that is expected. ![Image](https://github.com/user-attachments/assets/6ccedd77-ea8b-422e-a6fa-ce94448821d0)
Author
Owner

@arthur-lbchn commented on GitHub (Jul 11, 2025):

I have the similar issue on Ubuntu 24.04 LTS peers

Image
root@wol1-manage01:~# systemctl status netbird
● netbird.service - Netbird mesh network client
     Loaded: loaded (/etc/systemd/system/netbird.service; enabled; preset: enabled)
     Active: active (running) since Fri 2025-07-11 12:46:18 BST; 2min 13s ago
   Main PID: 515388 (netbird)
      Tasks: 11 (limit: 4598)
     Memory: 29.3M (peak: 30.2M)
        CPU: 766ms
     CGroup: /system.slice/netbird.service
             └─515388 /usr/bin/netbird service run --config /etc/netbird/config.json --log-level info --daemon-addr unix:///var/run/netbird.sock --log-file /var/log/netbird/client.log

I've also wiped all iptables rules to verify it's not client related.

PS. I don't have such issue on my MacOS personal device, the public IP address is properly recognized.

<!-- gh-comment-id:3061996064 --> @arthur-lbchn commented on GitHub (Jul 11, 2025): I have the similar issue on Ubuntu 24.04 LTS peers <img width="900" height="900" alt="Image" src="https://github.com/user-attachments/assets/622fe341-9ea2-4d46-b03b-564c25eec6ee" /> ``` root@wol1-manage01:~# systemctl status netbird ● netbird.service - Netbird mesh network client Loaded: loaded (/etc/systemd/system/netbird.service; enabled; preset: enabled) Active: active (running) since Fri 2025-07-11 12:46:18 BST; 2min 13s ago Main PID: 515388 (netbird) Tasks: 11 (limit: 4598) Memory: 29.3M (peak: 30.2M) CPU: 766ms CGroup: /system.slice/netbird.service └─515388 /usr/bin/netbird service run --config /etc/netbird/config.json --log-level info --daemon-addr unix:///var/run/netbird.sock --log-file /var/log/netbird/client.log ``` I've also wiped all iptables rules to verify it's not client related. PS. I don't have such issue on my MacOS personal device, the public IP address is properly recognized.
Author
Owner

@nazarewk commented on GitHub (Jul 11, 2025):

Thinking about it, it kind of makes sense when the "public" IP detection never exits the local network (both client and server are on the same LAN/router). There is no reasonable way to detect the "really public" IP without a 3rd party service running on the internet.

<!-- gh-comment-id:3062051523 --> @nazarewk commented on GitHub (Jul 11, 2025): Thinking about it, it kind of makes sense when the "public" IP detection never exits the local network (both client and server are on the same LAN/router). There is no reasonable way to detect the "really public" IP without a 3rd party service running on the internet.
Author
Owner

@arthur-lbchn commented on GitHub (Jul 11, 2025):

@nazarewk I'm afraid both client and server are not the same router, its devices located in the different datacenters

<!-- gh-comment-id:3062192054 --> @arthur-lbchn commented on GitHub (Jul 11, 2025): @nazarewk I'm afraid both client and server are not the same router, its devices located in the different datacenters
Author
Owner

@nazarewk commented on GitHub (Jul 11, 2025):

I heard back from the dev team, your issue is most likely caused by a reverse proxy misconfiguration of X-Forwarded-For HTTP header forwarding and/or assembling it.

The specific line seems to be:
9fbad7dbad/management/cmd/management.go (L241-L241)
coming from go-grpc-middleware

You might also want to take a look at X-Real-IP in addition to X-Forwarded-For

<!-- gh-comment-id:3062278547 --> @nazarewk commented on GitHub (Jul 11, 2025): I heard back from the dev team, your issue is most likely caused by a reverse proxy misconfiguration of `X-Forwarded-For` HTTP header forwarding and/or assembling it. The specific line seems to be: https://github.com/netbirdio/netbird/blob/9fbad7dbade7683a907af173546699a8ae765ac2/management/cmd/management.go#L241-L241 coming from [go-grpc-middleware](https://github.com/grpc-ecosystem/go-grpc-middleware/blob/cc754e60cb814827ec3915e8e27c9822d7856745/interceptors/realip/realip.go#L21-L25) You might also want to take a look at `X-Real-IP` in addition to `X-Forwarded-For`
Author
Owner

@arthur-lbchn commented on GitHub (Jul 11, 2025):

I just actually followed your guide https://docs.netbird.io/selfhosted/selfhosted-quickstart, so its all coming with default settings. I'll have a look into the headers, thank you.

<!-- gh-comment-id:3062538479 --> @arthur-lbchn commented on GitHub (Jul 11, 2025): I just actually followed your guide https://docs.netbird.io/selfhosted/selfhosted-quickstart, so its all coming with default settings. I'll have a look into the headers, thank you.
Author
Owner

@arthur-lbchn commented on GitHub (Jul 11, 2025):

Ah, looks like Netbird doesn't really support IPv6 which has a preference over IPv4 if its set, so docker wasn't able to get real IP until added to docker-compose.yml and recreated all containers:

networks:
  netbird:
    enable_ipv6: true

I hope IPv6 support is something on your to-do list. Thank you again!

<!-- gh-comment-id:3062918162 --> @arthur-lbchn commented on GitHub (Jul 11, 2025): Ah, looks like Netbird doesn't really support IPv6 which has a preference over IPv4 if its set, so docker wasn't able to get real IP until added to docker-compose.yml and recreated all containers: ``` networks: netbird: enable_ipv6: true ``` I hope IPv6 support is something on your to-do list. Thank you again!
Author
Owner

@nazarewk commented on GitHub (Jul 11, 2025):

NetBird isn't "officially" supporting external IPv6 (as opposed to internal NetBird IP range, which it officially does not support yet, see #46), but it seems to work just fine for most people in most settings they come up with.

With Could I'm connecting my dual-stack home network to dual-stack Hetzner VPN just fine, it's reporting IPv6 public IPs in the Dashboard and currently establishing P2P over IPv4 (I'm pretty sure it does occasionally establish v6 connectivity over my local ULA range and public IPs):

Image
 moss.netbird.cloud:
  NetBird IP: 100.79.63.47
  Public key: TWq...CdNX1lU=
  Status: Connected
  -- detail --
  Connection type: P2P
  ICE candidate (Local/Remote): srflx/prflx
  ICE candidate endpoints (Local/Remote): 94.000.000.123:7251/195.000.000.123:51819
  Relay server address: rels://streamline-de-fra1-1.relay.netbird.io:443
  Last connection update: 3 hours, 39 minutes ago
  Last WireGuard handshake: 10 seconds ago
  Transfer status (received/sent) 33.2 KiB/50.4 KiB
  Quantum resistance: false
  Networks: -
  Latency: 38.263351ms

I guess you have found the first not fully supported external IPv6 use case: running IPv6-based self-hosted management.

Thanks for the debugging effort so far. I would be eager to hear in case you got it fully working to update the deployment docs for v6.

<!-- gh-comment-id:3063032687 --> @nazarewk commented on GitHub (Jul 11, 2025): NetBird isn't "officially" supporting external IPv6 (as opposed to internal NetBird IP range, which it officially does not support yet, see #46), but it seems to work just fine for most people in most settings they come up with. With Could I'm connecting my dual-stack home network to dual-stack Hetzner VPN just fine, it's reporting IPv6 public IPs in the Dashboard and currently establishing P2P over IPv4 (I'm pretty sure it does occasionally establish v6 connectivity over my local ULA range and public IPs): <img width="1409" height="757" alt="Image" src="https://github.com/user-attachments/assets/dfa74753-301f-4e3b-9f26-5c201858647e" /> ```text moss.netbird.cloud: NetBird IP: 100.79.63.47 Public key: TWq...CdNX1lU= Status: Connected -- detail -- Connection type: P2P ICE candidate (Local/Remote): srflx/prflx ICE candidate endpoints (Local/Remote): 94.000.000.123:7251/195.000.000.123:51819 Relay server address: rels://streamline-de-fra1-1.relay.netbird.io:443 Last connection update: 3 hours, 39 minutes ago Last WireGuard handshake: 10 seconds ago Transfer status (received/sent) 33.2 KiB/50.4 KiB Quantum resistance: false Networks: - Latency: 38.263351ms ``` I guess you have found the first not fully supported external IPv6 use case: running IPv6-based self-hosted management. Thanks for the debugging effort so far. I would be eager to hear in case you got it fully working to update the deployment docs for v6.
Author
Owner

@aljazs-implex commented on GitHub (Nov 25, 2025):

I also have the issue where the reported client public IPs are actually the docker's internal GW address. Also running both server and clients on hetzner VMs.
Netbird is deployed via official setup steps and has both A and AAAA dns records.

The "solution" at the time was to remove the AAAA DNS record and have clients connect only via IPv4.

<!-- gh-comment-id:3577013535 --> @aljazs-implex commented on GitHub (Nov 25, 2025): I also have the issue where the reported client public IPs are actually the docker's internal GW address. Also running both server and clients on hetzner VMs. Netbird is deployed via official setup steps and has both A and AAAA dns records. The "solution" at the time was to remove the AAAA DNS record and have clients connect only via IPv4.
Author
Owner

@Muska-Ami commented on GitHub (Jan 26, 2026):

For who questioning about this issue, this might helpful: https://github.com/netbirdio/netbird/issues/1970#issuecomment-3798500658

<!-- gh-comment-id:3798540755 --> @Muska-Ami commented on GitHub (Jan 26, 2026): For who questioning about this issue, this might helpful: https://github.com/netbirdio/netbird/issues/1970#issuecomment-3798500658
Author
Owner

@LewisLosa commented on GitHub (Apr 6, 2026):

I have the same issue. I followed the steps in this video:
https://www.youtube.com/watch?v=ri3JvbylwS0

Setup Overview

  • VPS: NetBird server + Traefik + NetBird Proxy (Docker)
  • Home server: Proxmox (NOT Docker), connected via NetBird client

Network Topology

NetBird VPS network

Image

Home server network (Proxmox)

Image

proxy.env

NB_PROXY_DEBUG_LOGS=false
NB_PROXY_MANAGEMENT_ADDRESS=http://netbird-server:80
NB_PROXY_ALLOW_INSECURE=true
NB_PROXY_DOMAIN=sengozhome.losa.dev
NB_PROXY_ADDRESS=:8443
NB_PROXY_TOKEN=meow
NB_PROXY_CERTIFICATE_DIRECTORY=/certs
NB_PROXY_ACME_CERTIFICATES=true
NB_PROXY_ACME_CHALLENGE_TYPE=tls-alpn-01
NB_PROXY_FORWARDED_PROTO=https
NB_PROXY_PROXY_PROTOCOL=true
NB_PROXY_TRUSTED_PROXIES=172.30.0.10

config.yaml

server:
  listenAddress: ":80"
  exposedAddress: "https://sengozhome.losa.dev:443"
  stunPorts:
    - 3478
  metricsPort: 9090
  healthcheckAddress: ":9000"
  logLevel: "info"
  logFile: "console"

  authSecret: "meow"
  dataDir: "/var/lib/netbird"

  auth:
    issuer: "https://sengozhome.losa.dev/oauth2"
    signKeyRefreshEnabled: true
    dashboardRedirectURIs:
      - "https://sengozhome.losa.dev/nb-auth"
      - "https://sengozhome.losa.dev/nb-silent-auth"
    cliRedirectURIs:
      - "http://localhost:53000/"

  reverseProxy:
    trustedHTTPProxies:
      - "172.30.0.10/32"

  store:
    engine: "sqlite"
    encryptionKey: "meow"

traefik-dynamic.yaml

tcp:
  serversTransports:
    pp-v2:
      proxyProtocol:
        version: 2

docker-compose.yml

# (unchanged, full config below)
services:
  traefik:
    image: traefik:v3.6
    container_name: netbird-traefik
    restart: unless-stopped
    networks:
      netbird:
        ipv4_address: 172.30.0.10
    command:
      - "--log.level=INFO"
      - "--accesslog=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=netbird"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.websecure.allowACMEByPass=true"
      - "--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0"
      - "--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0"
      - "--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.letsencrypt.acme.email=meow"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--serverstransport.forwardingtimeouts.responseheadertimeout=0s"
      - "--serverstransport.forwardingtimeouts.idleconntimeout=0s"
      - "--providers.file.filename=/etc/traefik/dynamic.yaml"
    ports:
      - '443:443'
      - '80:80'
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - netbird_traefik_letsencrypt:/letsencrypt
      - ./traefik-dynamic.yaml:/etc/traefik/dynamic.yaml:ro

  dashboard:
    image: netbirdio/dashboard:latest
    container_name: netbird-dashboard
    restart: unless-stopped
    networks: [netbird]
    env_file:
      - ./dashboard.env
    labels:
      - traefik.enable=true
      - traefik.http.routers.netbird-dashboard.rule=Host(`sengozhome.losa.dev`)
      - traefik.http.routers.netbird-dashboard.entrypoints=websecure
      - traefik.http.routers.netbird-dashboard.tls=true
      - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt
      - traefik.http.services.dashboard.loadbalancer.server.port=80

  netbird-server:
    image: netbirdio/netbird-server:latest
    container_name: netbird-server
    restart: unless-stopped
    networks: [netbird]
    ports:
      - '3478:3478/udp'
    volumes:
      - netbird_data:/var/lib/netbird
      - ./config.yaml:/etc/netbird/config.yaml
    command: ["--config", "/etc/netbird/config.yaml"]

  proxy:
    image: netbirdio/reverse-proxy:latest
    container_name: netbird-proxy
    ports:
      - 51820:51820/udp
    restart: unless-stopped
    networks: [netbird]
    depends_on:
      - netbird-server
    env_file:
      - ./proxy.env
    volumes:
      - netbird_proxy_certs:/certs
    labels:
      - traefik.enable=true
      - traefik.tcp.routers.proxy-passthrough.entrypoints=websecure
      - traefik.tcp.routers.proxy-passthrough.rule=HostSNI(`*`)
      - traefik.tcp.routers.proxy-passthrough.tls.passthrough=true
      - traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443
      - traefik.tcp.services.proxy-tls.loadbalancer.serverstransport=pp-v2@file

volumes:
  netbird_data:
  netbird_traefik_letsencrypt:
  netbird_proxy_certs:

networks:
  netbird:
    driver: bridge
    ipam:
      config:
        - subnet: 172.30.0.0/24
          gateway: 172.30.0.1
<!-- gh-comment-id:4190779488 --> @LewisLosa commented on GitHub (Apr 6, 2026): I have the same issue. I followed the steps in this video: [https://www.youtube.com/watch?v=ri3JvbylwS0](https://www.youtube.com/watch?v=ri3JvbylwS0) ## Setup Overview * VPS: NetBird server + Traefik + NetBird Proxy (Docker) * Home server: Proxmox (NOT Docker), connected via NetBird client ## Network Topology ### NetBird VPS network <img width="604" height="713" alt="Image" src="https://github.com/user-attachments/assets/3b3622b0-641d-4916-81fb-925db64cf3ea" /> ### Home server network (Proxmox) <img width="577" height="763" alt="Image" src="https://github.com/user-attachments/assets/450e3c30-28be-4343-8720-0dfe657a7141" /> --- ## proxy.env ```env NB_PROXY_DEBUG_LOGS=false NB_PROXY_MANAGEMENT_ADDRESS=http://netbird-server:80 NB_PROXY_ALLOW_INSECURE=true NB_PROXY_DOMAIN=sengozhome.losa.dev NB_PROXY_ADDRESS=:8443 NB_PROXY_TOKEN=meow NB_PROXY_CERTIFICATE_DIRECTORY=/certs NB_PROXY_ACME_CERTIFICATES=true NB_PROXY_ACME_CHALLENGE_TYPE=tls-alpn-01 NB_PROXY_FORWARDED_PROTO=https NB_PROXY_PROXY_PROTOCOL=true NB_PROXY_TRUSTED_PROXIES=172.30.0.10 ``` --- ## config.yaml ```yaml server: listenAddress: ":80" exposedAddress: "https://sengozhome.losa.dev:443" stunPorts: - 3478 metricsPort: 9090 healthcheckAddress: ":9000" logLevel: "info" logFile: "console" authSecret: "meow" dataDir: "/var/lib/netbird" auth: issuer: "https://sengozhome.losa.dev/oauth2" signKeyRefreshEnabled: true dashboardRedirectURIs: - "https://sengozhome.losa.dev/nb-auth" - "https://sengozhome.losa.dev/nb-silent-auth" cliRedirectURIs: - "http://localhost:53000/" reverseProxy: trustedHTTPProxies: - "172.30.0.10/32" store: engine: "sqlite" encryptionKey: "meow" ``` --- ## traefik-dynamic.yaml ```yaml tcp: serversTransports: pp-v2: proxyProtocol: version: 2 ``` --- ## docker-compose.yml ```yaml # (unchanged, full config below) services: traefik: image: traefik:v3.6 container_name: netbird-traefik restart: unless-stopped networks: netbird: ipv4_address: 172.30.0.10 command: - "--log.level=INFO" - "--accesslog=true" - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--providers.docker.network=netbird" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--entrypoints.websecure.allowACMEByPass=true" - "--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0" - "--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0" - "--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0" - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https" - "--certificatesresolvers.letsencrypt.acme.email=meow" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true" - "--serverstransport.forwardingtimeouts.responseheadertimeout=0s" - "--serverstransport.forwardingtimeouts.idleconntimeout=0s" - "--providers.file.filename=/etc/traefik/dynamic.yaml" ports: - '443:443' - '80:80' volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - netbird_traefik_letsencrypt:/letsencrypt - ./traefik-dynamic.yaml:/etc/traefik/dynamic.yaml:ro dashboard: image: netbirdio/dashboard:latest container_name: netbird-dashboard restart: unless-stopped networks: [netbird] env_file: - ./dashboard.env labels: - traefik.enable=true - traefik.http.routers.netbird-dashboard.rule=Host(`sengozhome.losa.dev`) - traefik.http.routers.netbird-dashboard.entrypoints=websecure - traefik.http.routers.netbird-dashboard.tls=true - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt - traefik.http.services.dashboard.loadbalancer.server.port=80 netbird-server: image: netbirdio/netbird-server:latest container_name: netbird-server restart: unless-stopped networks: [netbird] ports: - '3478:3478/udp' volumes: - netbird_data:/var/lib/netbird - ./config.yaml:/etc/netbird/config.yaml command: ["--config", "/etc/netbird/config.yaml"] proxy: image: netbirdio/reverse-proxy:latest container_name: netbird-proxy ports: - 51820:51820/udp restart: unless-stopped networks: [netbird] depends_on: - netbird-server env_file: - ./proxy.env volumes: - netbird_proxy_certs:/certs labels: - traefik.enable=true - traefik.tcp.routers.proxy-passthrough.entrypoints=websecure - traefik.tcp.routers.proxy-passthrough.rule=HostSNI(`*`) - traefik.tcp.routers.proxy-passthrough.tls.passthrough=true - traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443 - traefik.tcp.services.proxy-tls.loadbalancer.serverstransport=pp-v2@file volumes: netbird_data: netbird_traefik_letsencrypt: netbird_proxy_certs: networks: netbird: driver: bridge ipam: config: - subnet: 172.30.0.0/24 gateway: 172.30.0.1 ```
Author
Owner

@yurix73 commented on GitHub (Apr 11, 2026):

I also have the issue where the reported client public IPs are actually the docker's internal GW address. Also running both server and clients on hetzner VMs. Netbird is deployed via official setup steps and has both A and AAAA dns records.

The "solution" at the time was to remove the AAAA DNS record and have clients connect only via IPv4.

I'm having the same problem. It occured on a Netcup VPS. I now installed Netbird on another VPS (Hetzner, Debian) to verify it isn't a fluke. I didn't add any AAAA records to make sure it's not an IPv6 problem. Same issue. I also followed the same official YouTube guide (Netbird + Authentik).

In the Proxy Events I mostly see the actual client Ipv4 address, however, for some "authentik.netbird.mydomain.com" calls the IP shows up as "172.30.0.1" - so the Docker gateway IP of the Netbird network. Also I locked myself out of my first instance's GUI, because I added a country access check to the authentik reverse proxy endpoint, which causes all IPs to be blacklisted that must first authenticate against Authentik.

Image Image

Some more info:

Netbird docker-compose

services:
  # Traefik reverse proxy (automatic TLS via Let's Encrypt)
  traefik:
    image: traefik:v3.6
    container_name: netbird-traefik
    restart: unless-stopped
    networks:
      netbird:
        ipv4_address: 172.30.0.10
    command:
      # Logging
      - "--log.level=INFO"
      - "--accesslog=true"
      # Docker provider
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=netbird"
      # Entrypoints
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.websecure.allowACMEByPass=true"
      # Disable timeouts for long-lived gRPC streams
      - "--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0"
      - "--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0"
      - "--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0"
      # HTTP to HTTPS redirect
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      # Let's Encrypt ACME
      - "--certificatesresolvers.letsencrypt.acme.email=CENSORED"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      # gRPC transport settings
      - "--serverstransport.forwardingtimeouts.responseheadertimeout=0s"
      - "--serverstransport.forwardingtimeouts.idleconntimeout=0s"
      - "--providers.file.filename=/etc/traefik/dynamic.yaml"
    ports:
      - '443:443'
      - '80:80'
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - netbird_traefik_letsencrypt:/letsencrypt
      - ./traefik-dynamic.yaml:/etc/traefik/dynamic.yaml:ro
    logging:
      driver: "json-file"
      options:
        max-size: "500m"
        max-file: "2"

  dashboard:
    image: netbirdio/dashboard:latest
    container_name: netbird-dashboard
    restart: unless-stopped
    networks: [netbird]
    env_file:
      - ./dashboard.env
    labels:
      - traefik.enable=true
      - traefik.http.routers.netbird-dashboard.rule=Host(`netbird.mydomain.com`)
      - traefik.http.routers.netbird-dashboard.entrypoints=websecure
      - traefik.http.routers.netbird-dashboard.tls=true
      - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt
      - traefik.http.routers.netbird-dashboard.service=dashboard
      - traefik.http.routers.netbird-dashboard.priority=1
      - traefik.http.services.dashboard.loadbalancer.server.port=80
    logging:
      driver: "json-file"
      options:
        max-size: "500m"
        max-file: "2"

  # Combined server (Management + Signal + Relay + STUN)
  netbird-server:
    image: netbirdio/netbird-server:latest
    container_name: netbird-server
    restart: unless-stopped
    networks: [netbird]
    ports:
      - '3478:3478/udp'
    volumes:
      - netbird_data:/var/lib/netbird
      - ./config.yaml:/etc/netbird/config.yaml
    command: ["--config", "/etc/netbird/config.yaml"]
    labels:
      - traefik.enable=true
      # gRPC router (needs h2c backend for HTTP/2 cleartext)
      - traefik.http.routers.netbird-grpc.rule=Host(`netbird.mydomain.com`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`))
      - traefik.http.routers.netbird-grpc.entrypoints=websecure
      - traefik.http.routers.netbird-grpc.tls=true
      - traefik.http.routers.netbird-grpc.tls.certresolver=letsencrypt
      - traefik.http.routers.netbird-grpc.service=netbird-server-h2c
      - traefik.http.routers.netbird-grpc.priority=100
      # Backend router (relay, WebSocket, API, OAuth2)
      - traefik.http.routers.netbird-backend.rule=Host(`netbird.mydomain.com`) && (PathPrefix(`/relay`) || PathPrefix(`/ws-proxy/`) || PathPrefix(`/api`) || PathPrefix(`/oauth2`))
      - traefik.http.routers.netbird-backend.entrypoints=websecure
      - traefik.http.routers.netbird-backend.tls=true
      - traefik.http.routers.netbird-backend.tls.certresolver=letsencrypt
      - traefik.http.routers.netbird-backend.service=netbird-server
      - traefik.http.routers.netbird-backend.priority=100
      # Services
      - traefik.http.services.netbird-server.loadbalancer.server.port=80
      - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80
      - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c
    logging:
      driver: "json-file"
      options:
        max-size: "500m"
        max-file: "2"

  # NetBird Proxy - exposes internal resources to the internet
  proxy:
    image: netbirdio/reverse-proxy:latest
    container_name: netbird-proxy
    ports:
    - 51820:51820/udp
    restart: unless-stopped
    networks: [netbird]
    depends_on:
      - netbird-server
    env_file:
      - ./proxy.env
    volumes:
      - netbird_proxy_certs:/certs
    labels:
      # TCP passthrough for any unmatched domain (proxy handles its own TLS)
      - traefik.enable=true
      - traefik.tcp.routers.proxy-passthrough.entrypoints=websecure
      - traefik.tcp.routers.proxy-passthrough.rule=HostSNI(`*`)
      - traefik.tcp.routers.proxy-passthrough.tls.passthrough=true
      - traefik.tcp.routers.proxy-passthrough.service=proxy-tls
      - traefik.tcp.routers.proxy-passthrough.priority=1
      - traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443
      - traefik.tcp.services.proxy-tls.loadbalancer.serverstransport=pp-v2@file
    logging:
      driver: "json-file"
      options:
        max-size: "500m"
        max-file: "2"

volumes:
  netbird_data:
  netbird_traefik_letsencrypt:
  netbird_proxy_certs:

networks:
  netbird:
    driver: bridge
    ipam:
      config:
        - subnet: 172.30.0.0/24
          gateway: 172.30.0.1

Netbird config.yaml

server:
  listenAddress: ":80"
  exposedAddress: "https://netbird.mydomain.com:443"
  stunPorts:
    - 3478
  metricsPort: 9090
  healthcheckAddress: ":9000"
  logLevel: "info"
  logFile: "console"

  authSecret: "Censored"
  dataDir: "/var/lib/netbird"

  auth:
    issuer: "https://netbird.mydomain.com/oauth2"
    signKeyRefreshEnabled: true
    dashboardRedirectURIs:
      - "https://netbird.mydomain.com/nb-auth"
      - "https://netbird.mydomain.com/nb-silent-auth"
    cliRedirectURIs:
      - "http://localhost:53000/"

  reverseProxy:
    trustedHTTPProxies:
      - "172.30.0.10/32"

  store:
    engine: "sqlite"
    encryptionKey: "Definitely Not"

Netbird traefik-dynamic.yaml

tcp:
  serversTransports:
    pp-v2:
      proxyProtocol:
        version: 2

Netbird proxy.env

# NetBird Proxy Configuration
NB_PROXY_DEBUG_LOGS=false
# Use internal Docker network to connect to management (avoids hairpin NAT issues)
NB_PROXY_MANAGEMENT_ADDRESS=http://netbird-server:80
# Allow insecure gRPC connection to management (required for internal Docker network)
NB_PROXY_ALLOW_INSECURE=true
# Public URL where this proxy is reachable (used for cluster registration)
NB_PROXY_DOMAIN=netbird.mydomain.com
NB_PROXY_ADDRESS=:8443
NB_PROXY_TOKEN=nbx_CENSORED
NB_PROXY_CERTIFICATE_DIRECTORY=/certs
NB_PROXY_ACME_CERTIFICATES=true
NB_PROXY_ACME_CHALLENGE_TYPE=tls-alpn-01
NB_PROXY_FORWARDED_PROTO=https
# Enable PROXY protocol to preserve client IPs through L4 proxies (Traefik TCP passthrough)
NB_PROXY_PROXY_PROTOCOL=true
# Trust Traefik's IP for PROXY protocol headers
NB_PROXY_TRUSTED_PROXIES=172.30.0.10

Authentik compose.yml

services:
  postgresql:
    env_file:
    - .env
    environment:
      POSTGRES_DB: ${PG_DB:-authentik}
      POSTGRES_PASSWORD: ${PG_PASS:?database password required}
      POSTGRES_USER: ${PG_USER:-authentik}
    healthcheck:
      interval: 30s
      retries: 5
      start_period: 20s
      test:
      - CMD-SHELL
      - pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}
      timeout: 5s
    image: docker.io/library/postgres:16-alpine
    restart: unless-stopped
    volumes:
    - database:/var/lib/postgresql/data
    networks:
      services:
        ipv4_address: 172.28.20.10
  server:
    command: server
    depends_on:
      postgresql:
        condition: service_healthy
    env_file:
    - .env
    environment:
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2.2}
    ports:
    - ${COMPOSE_PORT_HTTP:-9000}:9000
    - ${COMPOSE_PORT_HTTPS:-9443}:9443
    restart: unless-stopped
    shm_size: 512mb
    volumes:
    - ./data:/data
    - ./custom-templates:/templates
    networks:
      services:
        ipv4_address: 172.28.20.20
  worker:
    command: worker
    depends_on:
      postgresql:
        condition: service_healthy
    env_file:
    - .env
    environment:
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2.2}
    restart: unless-stopped
    shm_size: 512mb
    user: root
    volumes:
    - /var/run/docker.sock:/var/run/docker.sock
    - ./data:/data
    - ./certs:/certs
    - ./custom-templates:/templates
    networks:
      services:
        ipv4_address: 172.28.20.30

  netbird:
    image: netbirdio/netbird:latest
    container_name: netbird-client
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
      - SYS_ADMIN
      - SYS_RESOURCE
    environment:
      - NB_SETUP_KEY=CENSORED
      - NB_MANAGEMENT_URL=https://netbird.mydomain.com
      - NB_HOSTNAME=vps-services
    volumes:
      - ./netbird:/var/lib/netbird
    networks:
      services:
        ipv4_address: 172.28.20.40

networks:
  services:
    name: services
    driver: bridge
    ipam:
      config:
        - subnet: 172.28.20.0/24
          gateway: 172.28.20.1

volumes:
  database:
    driver: local
<!-- gh-comment-id:4230055170 --> @yurix73 commented on GitHub (Apr 11, 2026): > I also have the issue where the reported client public IPs are actually the docker's internal GW address. Also running both server and clients on hetzner VMs. Netbird is deployed via official setup steps and has both A and AAAA dns records. > > The "solution" at the time was to remove the AAAA DNS record and have clients connect only via IPv4. I'm having the same problem. It occured on a Netcup VPS. I now installed Netbird on another VPS (Hetzner, Debian) to verify it isn't a fluke. I didn't add any AAAA records to make sure it's not an IPv6 problem. Same issue. I also followed the same official YouTube guide (Netbird + Authentik). In the Proxy Events I mostly see the actual client Ipv4 address, however, for some "authentik.netbird.mydomain.com" calls the IP shows up as "172.30.0.1" - so the Docker gateway IP of the Netbird network. Also I locked myself out of my first instance's GUI, because I added a country access check to the authentik reverse proxy endpoint, which causes all IPs to be blacklisted that must first authenticate against Authentik. <img width="1600" height="775" alt="Image" src="https://github.com/user-attachments/assets/33aeaef6-1e02-43e5-a433-081a7869af03" /> <img width="445" height="457" alt="Image" src="https://github.com/user-attachments/assets/ed94e55d-a5e1-46a8-837c-253729a15df8" /> Some more info: Netbird docker-compose ```yaml services: # Traefik reverse proxy (automatic TLS via Let's Encrypt) traefik: image: traefik:v3.6 container_name: netbird-traefik restart: unless-stopped networks: netbird: ipv4_address: 172.30.0.10 command: # Logging - "--log.level=INFO" - "--accesslog=true" # Docker provider - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--providers.docker.network=netbird" # Entrypoints - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--entrypoints.websecure.allowACMEByPass=true" # Disable timeouts for long-lived gRPC streams - "--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0" - "--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0" - "--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0" # HTTP to HTTPS redirect - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https" # Let's Encrypt ACME - "--certificatesresolvers.letsencrypt.acme.email=CENSORED" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true" # gRPC transport settings - "--serverstransport.forwardingtimeouts.responseheadertimeout=0s" - "--serverstransport.forwardingtimeouts.idleconntimeout=0s" - "--providers.file.filename=/etc/traefik/dynamic.yaml" ports: - '443:443' - '80:80' volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - netbird_traefik_letsencrypt:/letsencrypt - ./traefik-dynamic.yaml:/etc/traefik/dynamic.yaml:ro logging: driver: "json-file" options: max-size: "500m" max-file: "2" dashboard: image: netbirdio/dashboard:latest container_name: netbird-dashboard restart: unless-stopped networks: [netbird] env_file: - ./dashboard.env labels: - traefik.enable=true - traefik.http.routers.netbird-dashboard.rule=Host(`netbird.mydomain.com`) - traefik.http.routers.netbird-dashboard.entrypoints=websecure - traefik.http.routers.netbird-dashboard.tls=true - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt - traefik.http.routers.netbird-dashboard.service=dashboard - traefik.http.routers.netbird-dashboard.priority=1 - traefik.http.services.dashboard.loadbalancer.server.port=80 logging: driver: "json-file" options: max-size: "500m" max-file: "2" # Combined server (Management + Signal + Relay + STUN) netbird-server: image: netbirdio/netbird-server:latest container_name: netbird-server restart: unless-stopped networks: [netbird] ports: - '3478:3478/udp' volumes: - netbird_data:/var/lib/netbird - ./config.yaml:/etc/netbird/config.yaml command: ["--config", "/etc/netbird/config.yaml"] labels: - traefik.enable=true # gRPC router (needs h2c backend for HTTP/2 cleartext) - traefik.http.routers.netbird-grpc.rule=Host(`netbird.mydomain.com`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`)) - traefik.http.routers.netbird-grpc.entrypoints=websecure - traefik.http.routers.netbird-grpc.tls=true - traefik.http.routers.netbird-grpc.tls.certresolver=letsencrypt - traefik.http.routers.netbird-grpc.service=netbird-server-h2c - traefik.http.routers.netbird-grpc.priority=100 # Backend router (relay, WebSocket, API, OAuth2) - traefik.http.routers.netbird-backend.rule=Host(`netbird.mydomain.com`) && (PathPrefix(`/relay`) || PathPrefix(`/ws-proxy/`) || PathPrefix(`/api`) || PathPrefix(`/oauth2`)) - traefik.http.routers.netbird-backend.entrypoints=websecure - traefik.http.routers.netbird-backend.tls=true - traefik.http.routers.netbird-backend.tls.certresolver=letsencrypt - traefik.http.routers.netbird-backend.service=netbird-server - traefik.http.routers.netbird-backend.priority=100 # Services - traefik.http.services.netbird-server.loadbalancer.server.port=80 - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80 - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c logging: driver: "json-file" options: max-size: "500m" max-file: "2" # NetBird Proxy - exposes internal resources to the internet proxy: image: netbirdio/reverse-proxy:latest container_name: netbird-proxy ports: - 51820:51820/udp restart: unless-stopped networks: [netbird] depends_on: - netbird-server env_file: - ./proxy.env volumes: - netbird_proxy_certs:/certs labels: # TCP passthrough for any unmatched domain (proxy handles its own TLS) - traefik.enable=true - traefik.tcp.routers.proxy-passthrough.entrypoints=websecure - traefik.tcp.routers.proxy-passthrough.rule=HostSNI(`*`) - traefik.tcp.routers.proxy-passthrough.tls.passthrough=true - traefik.tcp.routers.proxy-passthrough.service=proxy-tls - traefik.tcp.routers.proxy-passthrough.priority=1 - traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443 - traefik.tcp.services.proxy-tls.loadbalancer.serverstransport=pp-v2@file logging: driver: "json-file" options: max-size: "500m" max-file: "2" volumes: netbird_data: netbird_traefik_letsencrypt: netbird_proxy_certs: networks: netbird: driver: bridge ipam: config: - subnet: 172.30.0.0/24 gateway: 172.30.0.1 ``` Netbird config.yaml ```yaml server: listenAddress: ":80" exposedAddress: "https://netbird.mydomain.com:443" stunPorts: - 3478 metricsPort: 9090 healthcheckAddress: ":9000" logLevel: "info" logFile: "console" authSecret: "Censored" dataDir: "/var/lib/netbird" auth: issuer: "https://netbird.mydomain.com/oauth2" signKeyRefreshEnabled: true dashboardRedirectURIs: - "https://netbird.mydomain.com/nb-auth" - "https://netbird.mydomain.com/nb-silent-auth" cliRedirectURIs: - "http://localhost:53000/" reverseProxy: trustedHTTPProxies: - "172.30.0.10/32" store: engine: "sqlite" encryptionKey: "Definitely Not" ``` Netbird traefik-dynamic.yaml ```yaml tcp: serversTransports: pp-v2: proxyProtocol: version: 2 ``` Netbird proxy.env ``` # NetBird Proxy Configuration NB_PROXY_DEBUG_LOGS=false # Use internal Docker network to connect to management (avoids hairpin NAT issues) NB_PROXY_MANAGEMENT_ADDRESS=http://netbird-server:80 # Allow insecure gRPC connection to management (required for internal Docker network) NB_PROXY_ALLOW_INSECURE=true # Public URL where this proxy is reachable (used for cluster registration) NB_PROXY_DOMAIN=netbird.mydomain.com NB_PROXY_ADDRESS=:8443 NB_PROXY_TOKEN=nbx_CENSORED NB_PROXY_CERTIFICATE_DIRECTORY=/certs NB_PROXY_ACME_CERTIFICATES=true NB_PROXY_ACME_CHALLENGE_TYPE=tls-alpn-01 NB_PROXY_FORWARDED_PROTO=https # Enable PROXY protocol to preserve client IPs through L4 proxies (Traefik TCP passthrough) NB_PROXY_PROXY_PROTOCOL=true # Trust Traefik's IP for PROXY protocol headers NB_PROXY_TRUSTED_PROXIES=172.30.0.10 ``` Authentik compose.yml ```yaml services: postgresql: env_file: - .env environment: POSTGRES_DB: ${PG_DB:-authentik} POSTGRES_PASSWORD: ${PG_PASS:?database password required} POSTGRES_USER: ${PG_USER:-authentik} healthcheck: interval: 30s retries: 5 start_period: 20s test: - CMD-SHELL - pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER} timeout: 5s image: docker.io/library/postgres:16-alpine restart: unless-stopped volumes: - database:/var/lib/postgresql/data networks: services: ipv4_address: 172.28.20.10 server: command: server depends_on: postgresql: condition: service_healthy env_file: - .env environment: AUTHENTIK_POSTGRESQL__HOST: postgresql AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik} AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik} AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required} image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2.2} ports: - ${COMPOSE_PORT_HTTP:-9000}:9000 - ${COMPOSE_PORT_HTTPS:-9443}:9443 restart: unless-stopped shm_size: 512mb volumes: - ./data:/data - ./custom-templates:/templates networks: services: ipv4_address: 172.28.20.20 worker: command: worker depends_on: postgresql: condition: service_healthy env_file: - .env environment: AUTHENTIK_POSTGRESQL__HOST: postgresql AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik} AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik} AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required} image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2.2} restart: unless-stopped shm_size: 512mb user: root volumes: - /var/run/docker.sock:/var/run/docker.sock - ./data:/data - ./certs:/certs - ./custom-templates:/templates networks: services: ipv4_address: 172.28.20.30 netbird: image: netbirdio/netbird:latest container_name: netbird-client restart: unless-stopped cap_add: - NET_ADMIN - SYS_ADMIN - SYS_RESOURCE environment: - NB_SETUP_KEY=CENSORED - NB_MANAGEMENT_URL=https://netbird.mydomain.com - NB_HOSTNAME=vps-services volumes: - ./netbird:/var/lib/netbird networks: services: ipv4_address: 172.28.20.40 networks: services: name: services driver: bridge ipam: config: - subnet: 172.28.20.0/24 gateway: 172.28.20.1 volumes: database: driver: local ```
Author
Owner

@yurix73 commented on GitHub (Apr 11, 2026):

For who questioning about this issue, this might helpful: #1970 (comment)

I tried adding

  reverseProxy:
    trustedHTTPProxies:
      - "172.30.0.10/32"

and 172.30.0.0/24 to the netbird config.yaml trustedHTTPProxies, but that didn't work for me either. Is there any quick fix for this so I can use IPs for Access Control?

<!-- gh-comment-id:4230142782 --> @yurix73 commented on GitHub (Apr 11, 2026): > For who questioning about this issue, this might helpful: [#1970 (comment)](https://github.com/netbirdio/netbird/issues/1970#issuecomment-3798500658) I tried adding ``` reverseProxy: trustedHTTPProxies: - "172.30.0.10/32" ``` and `172.30.0.0/24` to the netbird config.yaml `trustedHTTPProxies`, but that didn't work for me either. Is there any quick fix for this so I can use IPs for Access Control?
Author
Owner

@Muska-Ami commented on GitHub (Apr 11, 2026):

Is there any quick fix for this so I can use IPs for Access Control?

I resolve this by filter the X-Forwarded-For header on nginx proxy, only left the client IP and then it's works. But you may do some additional setup on proxy to make sure the XFF header from a trusted proxy.

<!-- gh-comment-id:4230475286 --> @Muska-Ami commented on GitHub (Apr 11, 2026): > Is there any quick fix for this so I can use IPs for Access Control? I resolve this by filter the `X-Forwarded-For` header on nginx proxy, only left the client IP and then it's works. But you may do some additional setup on proxy to make sure the XFF header from a trusted proxy.
Author
Owner

@yurix73 commented on GitHub (Apr 12, 2026):

Is there any quick fix for this so I can use IPs for Access Control?

I resolve this by filter the X-Forwarded-For header on nginx proxy, only left the client IP and then it's works. But you may do some additional setup on proxy to make sure the XFF header from a trusted proxy.

Did you setup Netbird with Nginx instead of Traefik or do you have Nginx already running on the Host as a reverse proxy independently from Netbird?

<!-- gh-comment-id:4231063572 --> @yurix73 commented on GitHub (Apr 12, 2026): > > Is there any quick fix for this so I can use IPs for Access Control? > > I resolve this by filter the `X-Forwarded-For` header on nginx proxy, only left the client IP and then it's works. But you may do some additional setup on proxy to make sure the XFF header from a trusted proxy. Did you setup Netbird with Nginx instead of Traefik or do you have Nginx already running on the Host as a reverse proxy independently from Netbird?
Author
Owner

@FabianMartin commented on GitHub (Apr 23, 2026):

The problem is caused by missing IPv6 support in Docker. The following steps will fix it.

Step 1: Enable IPv6 in Docker

Open the Docker configuration file:
/etc/docker/daemon.json

Add the following:

{
  "ipv6":true
}

Then restart Docker:
systemctl restart docker

Step 2: Stop Netbird

docker compose down

Step 3: Update docker-compose.yml

Open the file with:
nano docker-compose.yml

Add the IPv6 configuration for the network:

services:
  traefik:
# ...
    networks:
      netbird:
        ipv4_address: 172.30.0.10
        ipv6_address: fda9:69ee:296b::10
# ...
networks:
  netbird:
    driver: bridge
    enable_ipv6: true
    ipam:
      config:
        - subnet: 172.30.0.0/24
          gateway: 172.30.0.1
        - subnet: fda9:69ee:296b::/64
          gateway: fda9:69ee:296b::1

Note: You can use any private subnet you like. If you need a random IPv6 subnet, you can generate one at https://unique-local-ipv6.com/

Step 4: Update config.yaml

Open the file with:
nano config.yaml

Add the proxy's IPv6 address to the list of trusted proxies:

# ....
  reverseProxy:
    trustedHTTPProxies:
      - "172.30.0.10/32"
      - "fda9:69ee:296b::10/128"
# ....

Step 5: Start Netbird

docker compose up -d

Done! IPv6 addresses should now appear in the logs.

Image
<!-- gh-comment-id:4303545773 --> @FabianMartin commented on GitHub (Apr 23, 2026): The problem is caused by missing IPv6 support in Docker. The following steps will fix it. **Step 1: Enable IPv6 in Docker** Open the Docker configuration file: `/etc/docker/daemon.json` Add the following: ```json { "ipv6":true } ``` Then restart Docker: `systemctl restart docker` **Step 2: Stop Netbird** ``` docker compose down ``` **Step 3: Update docker-compose.yml** Open the file with: `nano docker-compose.yml` Add the IPv6 configuration for the network: ```yaml services: traefik: # ... networks: netbird: ipv4_address: 172.30.0.10 ipv6_address: fda9:69ee:296b::10 # ... networks: netbird: driver: bridge enable_ipv6: true ipam: config: - subnet: 172.30.0.0/24 gateway: 172.30.0.1 - subnet: fda9:69ee:296b::/64 gateway: fda9:69ee:296b::1 ``` > **Note:** You can use any private subnet you like. If you need a random IPv6 subnet, you can generate one at https://unique-local-ipv6.com/ **Step 4: Update config.yaml** Open the file with: `nano config.yaml` Add the proxy's IPv6 address to the list of trusted proxies: ```yaml # .... reverseProxy: trustedHTTPProxies: - "172.30.0.10/32" - "fda9:69ee:296b::10/128" # .... ``` **Step 5: Start Netbird** ``` docker compose up -d ``` Done! IPv6 addresses should now appear in the logs. <img width="809" height="1048" alt="Image" src="https://github.com/user-attachments/assets/25ead437-d6bb-401a-a71b-2197892899a1" />
Sign in to join this conversation.
No Label self-hosting
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#3878