[GH-ISSUE #5384] Reverse Proxy with NB_PROXY_ACME_CERTIFICATES false not indicated correctly by dashboard #10473

Closed
opened 2026-08-05 01:26:02 -04:00 by saavagebueno · 13 comments
Owner

Originally created by @konstfish on GitHub (Feb 18, 2026).
Original GitHub issue: https://github.com/netbirdio/netbird/issues/5384

Describe the problem

When setting up the Reverse Proxy with NB_PROXY_ACME_CERTIFICATES set to false the management (or dashboard) will incorrectly display an "Issuing certificate..." spinner. This does not hinder the proxies functionality.

To Reproduce

Steps to reproduce the behavior:

  1. Set up a proxy instance with NB_PROXY_ACME_CERTIFICATES false & in my case a wildcard certificate generated by cert-manager. (Subject Alt Names proxy.example.com & *.proxy.example.com)
  2. Create a Service
    Image

I wrote blog post on the exact setup steps covering how to run the proxy on Kubernetes https://konst.fish/blog/NetBird-Reverse-Proxy-on-Kubernetes (If this counts as self promotion I'm happy to remove it from the issue)

Expected behavior

The dashboard should indicate the self-managed certificate status of the proxy.

Are you using NetBird Cloud?

Self-hosted using the Helm Chart from netbirdio/helms

NetBird version

v0.65.2 (proxy & mgmt), v2.32.3 (dashboard)

Is any other VPN software installed?

No

Have you tried these troubleshooting steps?

  • Reviewed client troubleshooting (if applicable)
  • Checked for newer NetBird versions
  • Searched for similar issues on GitHub (including closed ones)
  • Restarted the NetBird client
  • Disabled other VPN software
  • Checked firewall settings

Loving the proxy so far! I have a question kind of related to this issue, I'm assuming this isn't the case, but will the proxy support serving http in the future without any required certificate setup? This would make the deployment for infrastructures with existing ingress controllers a bit easier.

Originally created by @konstfish on GitHub (Feb 18, 2026). Original GitHub issue: https://github.com/netbirdio/netbird/issues/5384 **Describe the problem** When setting up the Reverse Proxy with `NB_PROXY_ACME_CERTIFICATES` set to `false` the management (or dashboard) will incorrectly display an "Issuing certificate..." spinner. This does not hinder the proxies functionality. **To Reproduce** Steps to reproduce the behavior: 1. Set up a proxy instance with `NB_PROXY_ACME_CERTIFICATES` false & in my case a wildcard certificate generated by cert-manager. (Subject Alt Names proxy.example.com & *.proxy.example.com) 2. Create a Service <img height="296" alt="Image" src="https://github.com/user-attachments/assets/c070b1e5-7984-43f3-813a-72bbcc173a8a" /> I wrote blog post on the exact setup steps covering how to run the proxy on Kubernetes https://konst.fish/blog/NetBird-Reverse-Proxy-on-Kubernetes (If this counts as self promotion I'm happy to remove it from the issue) **Expected behavior** The dashboard should indicate the self-managed certificate status of the proxy. **Are you using NetBird Cloud?** Self-hosted using the Helm Chart from netbirdio/helms **NetBird version** `v0.65.2` (proxy & mgmt), `v2.32.3` (dashboard) **Is any other VPN software installed?** No **Have you tried these troubleshooting steps?** - [ ] Reviewed [client troubleshooting](https://docs.netbird.io/how-to/troubleshooting-client) (if applicable) - [x] Checked for newer NetBird versions - [ ] Searched for similar issues on GitHub (including closed ones) - [ ] Restarted the NetBird client - [ ] Disabled other VPN software - [ ] Checked firewall settings Loving the proxy so far! I have a question kind of related to this issue, I'm assuming this isn't the case, but will the proxy support serving http in the future without any required certificate setup? This would make the deployment for infrastructures with existing ingress controllers a bit easier.
saavagebueno added the triage-needed label 2026-08-05 01:26:02 -04:00
Author
Owner

@konstfish commented on GitHub (Mar 17, 2026):

Resolved in Management v0.66.4 & Dashboard v2.34.2

<!-- gh-comment-id:4078181049 --> @konstfish commented on GitHub (Mar 17, 2026): Resolved in Management v0.66.4 & Dashboard v2.34.2
Author
Owner

@Nathanwoodburn commented on GitHub (Mar 19, 2026):

G'day,
I have the same issue on my netbird installation while on Management: v0.66.4, Dashboard: v2.34.2.

I've pulled the latest containers netbirdio/dashboard:latest and netbirdio/management:latest and restarted the stack but it still shows the issuing message even though the certificate is working correctly

Image
<!-- gh-comment-id:4095175301 --> @Nathanwoodburn commented on GitHub (Mar 19, 2026): G'day, I have the same issue on my netbird installation while on Management: v0.66.4, Dashboard: v2.34.2. I've pulled the latest containers `netbirdio/dashboard:latest` and `netbirdio/management:latest` and restarted the stack but it still shows the issuing message even though the certificate is working correctly <img width="1195" height="1126" alt="Image" src="https://github.com/user-attachments/assets/73ef8306-ffa8-49f3-8857-46c142444cb6" />
Author
Owner

@GEAR-IT-UP commented on GitHub (Mar 28, 2026):

I can also confirm this has not been fixed. I have the cert set to false (as I went to cloudflare, made it issue a cert and kept it and the key locally. The CNAME/CAA works and issues correctly but... still is set to constantly issuing

Image
<!-- gh-comment-id:4148971618 --> @GEAR-IT-UP commented on GitHub (Mar 28, 2026): I can also confirm this has not been fixed. I have the cert set to false (as I went to cloudflare, made it issue a cert and kept it and the key locally. The CNAME/CAA works and issues correctly but... still is set to constantly issuing <img width="2548" height="980" alt="Image" src="https://github.com/user-attachments/assets/902e02b3-7cae-4cb6-a536-6f825bdfcba4" />
Author
Owner

@konstfish commented on GitHub (Mar 30, 2026):

Re-opening, but I'm unfortunately unable to reproduce on my side

<!-- gh-comment-id:4154593884 --> @konstfish commented on GitHub (Mar 30, 2026): Re-opening, but I'm unfortunately unable to reproduce on my side
Author
Owner

@GEAR-IT-UP commented on GitHub (Mar 30, 2026):

So to confirm, you have a local cert you create or got from somewhere like cloudflare (in my case) and then you created a domain/custom domain (custom in my case, to have 1 level, not two like *.netbird.domain.com) as I wanted *.domain.com instead

My instances and cname/a records are proxied by cloudflare. So I needed a cert downloaded from them to get it to work, setting it to full strict etc.

You then I assume, go to create a service using that local cert and it just instantly issues for you ?

I have like 20 services and they all work as expected but issuing certificate... still. Its been a few days.

To add i removed the proxy record for a record (i think it was the wildcard cname to get a ticket beside the custom domain) as it only checks once.

Maybe there should be a "IF local cert, once successful connection to the service has been serviced, tick"?

<!-- gh-comment-id:4154651420 --> @GEAR-IT-UP commented on GitHub (Mar 30, 2026): So to confirm, you have a local cert you create or got from somewhere like cloudflare (in my case) and then you created a domain/custom domain (custom in my case, to have 1 level, not two like *.netbird.domain.com) as I wanted *.domain.com instead My instances and cname/a records are proxied by cloudflare. So I needed a cert downloaded from them to get it to work, setting it to full strict etc. You then I assume, go to create a service using that local cert and it just instantly issues for you ? I have like 20 services and they all work as expected but issuing certificate... still. Its been a few days. To add i removed the proxy record for a record (i think it was the wildcard cname to get a ticket beside the custom domain) as it only checks once. Maybe there should be a "IF local cert, once successful connection to the service has been serviced, tick"?
Author
Owner

@konstfish commented on GitHub (Mar 30, 2026):

Yes, cert issued through cert-manager in my case for "*.proxy.example.com" and "proxy.example.com" which I provide the proxy with directly. NB_PROXY_ACME_CERTIFICATES: false causes it to not create one itself but instead use the provided certificate. I believe this flow wasn't considered and messes with either the controllers or the dashboards state, causing it to display the "Issuing certificate..." spinner, waiting for the proxy to provide feedback that the request to Let's Encrypt was successful, which is never the case for our setups, since we provide the cert.

When I spin up proxies on the latest versions right now tho I can't reproduce this spinner anymore.

<!-- gh-comment-id:4154823122 --> @konstfish commented on GitHub (Mar 30, 2026): Yes, cert issued through cert-manager in my case for "*.proxy.example.com" and "proxy.example.com" which I provide the proxy with directly. `NB_PROXY_ACME_CERTIFICATES: false` causes it to not create one itself but instead use the provided certificate. I believe this flow wasn't considered and messes with either the controllers or the dashboards state, causing it to display the "Issuing certificate..." spinner, waiting for the proxy to provide feedback that the request to Let's Encrypt was successful, which is never the case for our setups, since we provide the cert. When I spin up proxies on the latest versions right now tho I can't reproduce this spinner anymore.
Author
Owner

@GEAR-IT-UP commented on GitHub (Mar 30, 2026):

What would you need from me to help? Compose and all that?

I did make some changes to it (claude did its best lol) which could point to the issue.

I wonder if (in other open issues) if dns-01 for cloudflare proxy and api key (like how nginx proxy manager etc work) would solve this 🤔

As then some edits wont needed to be made to the expected flow

<!-- gh-comment-id:4154855848 --> @GEAR-IT-UP commented on GitHub (Mar 30, 2026): What would you need from me to help? Compose and all that? I did make some changes to it (claude did its best lol) which could point to the issue. I wonder if (in other open issues) if dns-01 for cloudflare proxy and api key (like how nginx proxy manager etc work) would solve this 🤔 As then some edits wont needed to be made to the expected flow
Author
Owner

@GEAR-IT-UP commented on GitHub (Mar 30, 2026):

is it possible for you to confirm which items need to changed on the docker compose/proxy env for local certs to work. I can maybe start again on the latest and see if that fixes anything? (i am on the current container versions shown below)

Image

current versions

Image

issue from what I can see

below il post whats in my configs:

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=redacted
# 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=redacted
NB_PROXY_ADDRESS=:8443
NB_PROXY_TOKEN=redacted
NB_PROXY_CERTIFICATE_DIRECTORY=/certs
#NB_PROXY_ACME_CERTIFICATES=true
#NB_PROXY_ACME_CHALLENGE_TYPE=tls-alpn-01
NB_PROXY_ACME_CERTIFICATES=false
NB_PROXY_CERTIFICATE_FILE=tls.crt
NB_PROXY_CERTIFICATE_KEY_FILE=tls.key
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

traefi-dynamic.yaml has no changes, same with config.yaml + dashboard.env

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=DEBUG"
      - "--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=redacted"
#      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
#      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--certificatesresolvers.cloudflare.acme.dnschallenge=true"
      - "--certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare"
      - "--certificatesresolvers.cloudflare.acme.email=redacted"
      - "--certificatesresolvers.cloudflare.acme.storage=/letsencrypt/acme.json"

      # gRPC transport settings
      - "--serverstransport.forwardingtimeouts.responseheadertimeout=0s"
      - "--serverstransport.forwardingtimeouts.idleconntimeout=0s"
      - "--providers.file.filename=/etc/traefik/dynamic.yaml"
    environment:
      - CF_DNS_API_TOKEN=redacted
    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"

  # UI dashboard
  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.redacted`)
      - 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.tls.certresolver=cloudflare

      - 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(`redacted`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`))
      - traefik.http.routers.netbird-grpc.rule=Host(`redacted`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`) || PathPrefix(`/management.ProxyService/`))
      - 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.tls.certresolver=cloudflare

      - 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(`redacted`) && (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.tls.certresolver=cloudflare
      - 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"

  netbird-client:
    container_name: netbird-client
    hostname: netbird-lxc
    image: netbirdio/netbird:latest
    cap_add:
      - NET_ADMIN
      - SYS_ADMIN
      - SYS_RESOURCE
    devices:
      - /dev/net/tun
    network_mode: host
    environment:
      - NB_SETUP_KEY=redacted
      - NB_MANAGEMENT_URL=redacted
    volumes:
    - netbird-client:/var/lib/netbird
    restart: unless-stopped

volumes:
  netbird_data:
  netbird_traefik_letsencrypt:
  netbird_proxy_certs:
  netbird-client:
    name: netbird-client

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

<!-- gh-comment-id:4157902128 --> @GEAR-IT-UP commented on GitHub (Mar 30, 2026): is it possible for you to confirm which items need to changed on the docker compose/proxy env for local certs to work. I can maybe start again on the latest and see if that fixes anything? (i am on the current container versions shown below) <img width="177" height="88" alt="Image" src="https://github.com/user-attachments/assets/18f40d5d-ab7b-4525-bac0-3214afc3ef06" /> current versions <img width="788" height="1001" alt="Image" src="https://github.com/user-attachments/assets/42904e7f-85e2-499e-9bcf-20688b370e59" /> issue from what I can see below il post whats in my configs: 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=redacted # 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=redacted NB_PROXY_ADDRESS=:8443 NB_PROXY_TOKEN=redacted NB_PROXY_CERTIFICATE_DIRECTORY=/certs #NB_PROXY_ACME_CERTIFICATES=true #NB_PROXY_ACME_CHALLENGE_TYPE=tls-alpn-01 NB_PROXY_ACME_CERTIFICATES=false NB_PROXY_CERTIFICATE_FILE=tls.crt NB_PROXY_CERTIFICATE_KEY_FILE=tls.key 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 ``` traefi-dynamic.yaml has no changes, same with config.yaml + dashboard.env 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=DEBUG" - "--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=redacted" # - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" # - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true" - "--certificatesresolvers.cloudflare.acme.dnschallenge=true" - "--certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare" - "--certificatesresolvers.cloudflare.acme.email=redacted" - "--certificatesresolvers.cloudflare.acme.storage=/letsencrypt/acme.json" # gRPC transport settings - "--serverstransport.forwardingtimeouts.responseheadertimeout=0s" - "--serverstransport.forwardingtimeouts.idleconntimeout=0s" - "--providers.file.filename=/etc/traefik/dynamic.yaml" environment: - CF_DNS_API_TOKEN=redacted 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" # UI dashboard 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.redacted`) - 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.tls.certresolver=cloudflare - 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(`redacted`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`)) - traefik.http.routers.netbird-grpc.rule=Host(`redacted`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`) || PathPrefix(`/management.ProxyService/`)) - 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.tls.certresolver=cloudflare - 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(`redacted`) && (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.tls.certresolver=cloudflare - 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" netbird-client: container_name: netbird-client hostname: netbird-lxc image: netbirdio/netbird:latest cap_add: - NET_ADMIN - SYS_ADMIN - SYS_RESOURCE devices: - /dev/net/tun network_mode: host environment: - NB_SETUP_KEY=redacted - NB_MANAGEMENT_URL=redacted volumes: - netbird-client:/var/lib/netbird restart: unless-stopped volumes: netbird_data: netbird_traefik_letsencrypt: netbird_proxy_certs: netbird-client: name: netbird-client networks: netbird: name: netbird driver: bridge ipam: config: - subnet: 172.30.0.0/24 gateway: 172.30.0.1 ```
Author
Owner

@GEAR-IT-UP commented on GitHub (Apr 4, 2026):

Yes, cert issued through cert-manager in my case for "*.proxy.example.com" and "proxy.example.com" which I provide the proxy with directly. NB_PROXY_ACME_CERTIFICATES: false causes it to not create one itself but instead use the provided certificate. I believe this flow wasn't considered and messes with either the controllers or the dashboards state, causing it to display the "Issuing certificate..." spinner, waiting for the proxy to provide feedback that the request to Let's Encrypt was successful, which is never the case for our setups, since we provide the cert.

When I spin up proxies on the latest versions right now tho I can't reproduce this spinner anymore.

Hey, Any updates at all?

I sent the compose I used above. Maybe you can let me know what you modify to get the result you get (hopefully its just something am doing wrong but i just updated to the release from yesterday, issued a new cert and get the same result. (working but GUI states still issuing). Ive not tried starting again, so maybe that will work? let me know what you add/change thanks!

<!-- gh-comment-id:4187795389 --> @GEAR-IT-UP commented on GitHub (Apr 4, 2026): > Yes, cert issued through cert-manager in my case for "*.proxy.example.com" and "proxy.example.com" which I provide the proxy with directly. `NB_PROXY_ACME_CERTIFICATES: false` causes it to not create one itself but instead use the provided certificate. I believe this flow wasn't considered and messes with either the controllers or the dashboards state, causing it to display the "Issuing certificate..." spinner, waiting for the proxy to provide feedback that the request to Let's Encrypt was successful, which is never the case for our setups, since we provide the cert. > > When I spin up proxies on the latest versions right now tho I can't reproduce this spinner anymore. Hey, Any updates at all? I sent the compose I used above. Maybe you can let me know what you modify to get the result you get (hopefully its just something am doing wrong but i just updated to the release from yesterday, issued a new cert and get the same result. (working but GUI states still issuing). Ive not tried starting again, so maybe that will work? let me know what you add/change thanks!
Author
Owner

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

TL;DR
for wildcard certificates files, set NB_PROXY_ACME_CERTIFICATES=true & NB_PROXY_WILDCARD_CERT_DIR=..., even if not relying on ACME

Hi, In my case, I think I found the source of the confusion......

I am using my own wildcard certificate, and in my setup NB_PROXY_ACME_CERTIFICATES=true together with NB_PROXY_WILDCARD_CERT_DIR, even if not relying on ACME

After looking at the code, this now makes more sense to me:
in proxy/server.go, NB_PROXY_WILDCARD_CERT_DIR is only wired in when the ACME manager is created, so keeping NB_PROXY_ACME_CERTIFICATES=true seems to be required in order to activate wildcard certificate support.

Then, in proxy/internal/acme/manager.go, loadWildcardDir() scans the wildcard directory for *.crt files, looks for the matching .key file with the same basename, and extracts wildcard SANs from the certificate. Also, GetCertificate() checks the loaded wildcard certificates first, and AddDomain() immediately marks the domain as ready if it matches one of those wildcards.

So in my case, even though ACME is enabled, the domains covered by my wildcard certificate are served by the static wildcard cert from NB_PROXY_WILDCARD_CERT_DIR, not by a newly issued ACME certificate.

So my remaining question is: is this intentional?
Because the current behavior, together with the variable names and status text, feels a bit misleading....

Relevant files:

  • proxy/server.go
  • proxy/internal/acme/manager.go
  • proxy/cmd/proxy/cmd/root.go
<!-- gh-comment-id:4195579655 --> @gpillon commented on GitHub (Apr 6, 2026): TL;DR for wildcard certificates files, set `NB_PROXY_ACME_CERTIFICATES=true` & `NB_PROXY_WILDCARD_CERT_DIR=...`, even if not relying on ACME Hi, In my case, I think I found the source of the confusion...... I am using my own wildcard certificate, and in my setup `NB_PROXY_ACME_CERTIFICATES=true` together with `NB_PROXY_WILDCARD_CERT_DIR`, **even if not relying on ACME** After looking at the code, this now makes more sense to me: in `proxy/server.go`, `NB_PROXY_WILDCARD_CERT_DIR` is only wired in when the ACME manager is created, so keeping `NB_PROXY_ACME_CERTIFICATES=true` seems to be required in order to activate wildcard certificate support. Then, in `proxy/internal/acme/manager.go`, `loadWildcardDir()` scans the wildcard directory for `*.crt` files, looks for the matching `.key` file with the same basename, and extracts wildcard SANs from the certificate. Also, `GetCertificate()` checks the loaded wildcard certificates first, and `AddDomain()` immediately marks the domain as ready if it matches one of those wildcards. So in my case, even though ACME is enabled, the domains covered by my wildcard certificate are served by the static wildcard cert from `NB_PROXY_WILDCARD_CERT_DIR`, not by a newly issued ACME certificate. So my remaining question is: **_is this intentional?_** Because the current behavior, together with the variable names and status text, feels a bit misleading.... Relevant files: * `proxy/server.go` * `proxy/internal/acme/manager.go` * `proxy/cmd/proxy/cmd/root.go`
Author
Owner

@konstfish commented on GitHub (Apr 8, 2026):

@GEAR-IT-UP Our setups more or less look the same, my Kube deployment below:

spec:
  replicas: 3
# ...
    spec:
      containers:
        - name: reverse-proxy
          image: netbirdio/reverse-proxy:0.68.1
          env:
            - name: NB_PROXY_TOKEN
              valueFrom:
                secretKeyRef:
                  name: netbird-proxy-token
                  key: token
            - name: NB_PROXY_DEBUG_LOGS
              value: "true"
            - name: NB_PROXY_MANAGEMENT_ADDRESS
              value: "https://netbird.example.com:443"
            - name: NB_PROXY_DOMAIN
              value: "proxy.example.com"
            - name: NB_PROXY_ACME_CERTIFICATES
              value: "false"
            - name: NB_PROXY_CERTIFICATE_DIRECTORY
              value: "/certs"
# ...
          volumeMounts:
            - name: certs
              mountPath: /certs
              readOnly: true
      volumes:
        - name: certs
          secret:
            secretName: proxy-example-com

Note again as well that my cert has two subjectAltNames for proxy.example.com & *.proxy.example.com so that might be a difference, this for me results in no cert spinner:

https://github.com/user-attachments/assets/d9090fbc-330a-4faa-8498-9a2af4e5b00c

<!-- gh-comment-id:4209604774 --> @konstfish commented on GitHub (Apr 8, 2026): @GEAR-IT-UP Our setups more or less look the same, my Kube deployment below: ```yaml spec: replicas: 3 # ... spec: containers: - name: reverse-proxy image: netbirdio/reverse-proxy:0.68.1 env: - name: NB_PROXY_TOKEN valueFrom: secretKeyRef: name: netbird-proxy-token key: token - name: NB_PROXY_DEBUG_LOGS value: "true" - name: NB_PROXY_MANAGEMENT_ADDRESS value: "https://netbird.example.com:443" - name: NB_PROXY_DOMAIN value: "proxy.example.com" - name: NB_PROXY_ACME_CERTIFICATES value: "false" - name: NB_PROXY_CERTIFICATE_DIRECTORY value: "/certs" # ... volumeMounts: - name: certs mountPath: /certs readOnly: true volumes: - name: certs secret: secretName: proxy-example-com ``` Note again as well that my cert has two subjectAltNames for proxy.example.com & *.proxy.example.com so that might be a difference, this for me results in no cert spinner: https://github.com/user-attachments/assets/d9090fbc-330a-4faa-8498-9a2af4e5b00c
Author
Owner

@GEAR-IT-UP commented on GitHub (Apr 9, 2026):

Managed to get this sorted on a straight Docker setup with a wildcard cert — two things needed fixing:

  1. Wrong variable for wildcard certs

As gpillon pointed out from the code, NB_PROXY_WILDCARD_CERT_DIR only gets wired in when the ACME manager is created, so you need NB_PROXY_ACME_CERTIFICATES=true even when you're providing your own cert. Using false with the static file variables just leaves the proxy with nothing to work with (I think)...

proxy.env

before:
NB_PROXY_ACME_CERTIFICATES=false
NB_PROXY_CERTIFICATE_DIRECTORY=/certs
NB_PROXY_CERTIFICATE_FILE=tls.crt
NB_PROXY_CERTIFICATE_KEY_FILE=tls.key

After:
NB_PROXY_ACME_CERTIFICATES=true
NB_PROXY_WILDCARD_CERT_DIR=/certs

  1. Named volume had no cert files in it

The compose was using a named volume (netbird_proxy_certs:/certs) which doesn't automatically contain anything. Swapped to a bind mount with the actual cert files in it:

  • ./certs:/certs:ro

Claude suggests... Files go in ./certs/ named by basename — proxy scans for *.crt and matches the .key with the same name. So, I spun it back up and the spinner cleared straight away on all services. (no redoing of the previous services).

I can also confirm this working behind cloudflare proxy with no items unproxied.

@konstfish maybe for netbird's guides, that might do as a stop gap for those wanting proxied instances for self hosting until netbird supports DNS-01 challenges via dashboard like Traefik/Nginx etc... I think there is like 3 or 4 issues opened requesting this.

Working on 0.68.1 management and 2.36.0 dashboard (current latest).

Image

TLDR: Mark as complete but somewhere in the readme or a guide on netbird, it would be good to show this working as a stop gap for others wanting proxied cloudflare instances/offline certs.

<!-- gh-comment-id:4217928919 --> @GEAR-IT-UP commented on GitHub (Apr 9, 2026): Managed to get this sorted on a straight Docker setup with a wildcard cert — two things needed fixing: 1. Wrong variable for wildcard certs As gpillon pointed out from the code, NB_PROXY_WILDCARD_CERT_DIR only gets wired in when the ACME manager is created, so you need NB_PROXY_ACME_CERTIFICATES=true even when you're providing your own cert. Using false with the static file variables just leaves the proxy with nothing to work with (I think)... proxy.env before: NB_PROXY_ACME_CERTIFICATES=false NB_PROXY_CERTIFICATE_DIRECTORY=/certs NB_PROXY_CERTIFICATE_FILE=tls.crt NB_PROXY_CERTIFICATE_KEY_FILE=tls.key After: NB_PROXY_ACME_CERTIFICATES=true NB_PROXY_WILDCARD_CERT_DIR=/certs 2. Named volume had no cert files in it The compose was using a named volume (netbird_proxy_certs:/certs) which doesn't automatically contain anything. Swapped to a bind mount with the actual cert files in it: - ./certs:/certs:ro Claude suggests... Files go in ./certs/ named by basename — proxy scans for *.crt and matches the .key with the same name. So, I spun it back up and the spinner cleared straight away on all services. (no redoing of the previous services). I can also confirm this working behind cloudflare proxy with no items unproxied. @konstfish maybe for netbird's guides, that might do as a stop gap for those wanting proxied instances for self hosting until netbird supports DNS-01 challenges via dashboard like Traefik/Nginx etc... I think there is like 3 or 4 issues opened requesting this. Working on 0.68.1 management and 2.36.0 dashboard (current latest). <img width="1316" height="1300" alt="Image" src="https://github.com/user-attachments/assets/0a03cc86-fd8e-4184-9b6f-1ae976e3ab67" /> TLDR: Mark as complete but somewhere in the readme or a guide on netbird, it would be good to show this working as a stop gap for others wanting proxied cloudflare instances/offline certs.
Author
Owner

@konstfish commented on GitHub (Apr 15, 2026):

Thank you for the final writeup!

<!-- gh-comment-id:4251001362 --> @konstfish commented on GitHub (Apr 15, 2026): Thank you for the final writeup!
Sign in to join this conversation.
No Label triage-needed
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#10473