Can i use Static certificates & Letsencrypt together?

Hi everyone,

I’ve recently self-hosted netbird instance, I’m using the netbird resources which are in beta currently. I’ve added static certificate for handle SSL across all my subdomains, it was working good.

But when i create service with custom domain they’re not working, any help!

I’m using these netbird versions:
Management: v0.71.4
Dashboard: v2.38.1

The service has to use the netbird proxy, did you setup that component? After that in most cases, you’d need to have *.proxy.domain.com, what domain is setup on the proxy settings for you then?

This was my current configuration:

netbird dashboard hosted on: netbird.akhilnaidu.com
services: *.netbird.akhilnaidu.com

I have a working setup with static wildcard certificates for *.netbird.akhilnaidu.com, all services under that domain get SSL without Let’s Encrypt generating individual certs, which works great.

However, I recently attached a custom domain (portfolio.pavanbhaskar.com) to a service via the reverse-proxy section. SSL is not being generated for this custom domain.

The question is: when a custom domain is attached to a service, does the proxy/Traefik attempt to issue a Let’s Encrypt certificate for it automatically or does the static certificate configuration interfere with that flow?

Is this a known limitation, or is there additional configuration needed to support both static wildcard certs and Let’s Encrypt-issued certs for custom domains simultaneously?

Here’re my configuration files:

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=pavan.challa@resonateaes.com"
      - "--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"

  # 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.akhilnaidu.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.akhilnaidu.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.akhilnaidu.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:
        condition: service_started
    env_file:
      - ./proxy.env
    volumes:
      - /opt/netbird/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:

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

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.akhilnaidu.com
NB_PROXY_ADDRESS=:8443
NB_PROXY_TOKEN=nbx_GD4SR1eoLO6kJpTQ2ZIeDVcObnb98B2VshEA

NB_PROXY_CERTIFICATE_DIRECTORY=/certs
NB_PROXY_CERTIFICATE_FILE=fullchain.pem
NB_PROXY_CERTIFICATE_KEY_FILE=privkey.pem

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

It might be having an issue with this because it is trying to get *.phavanbhaskar.com instead of your portfolio certificate. Keep in mind it needs a cname on the domain itself and the * of the domain to propely verify to your domain (and have cloudflare on gray if you use that)

I haven’t turned on proxy option for dns record

Screenshot 2026-05-25 at 4.21.01 PM

GHmm, weird. What are your proxy logs when issuing the certificate?

netbird-proxy  | 2026-05-25T10:47:22.132Z INFO [account_id: d87vc5sda4bc73ft5ukg, service_key: domain:portfolio.pavanbhaskar.com] proxy/internal/roundtrip/netbird.go:253: created new client for account
netbird-proxy  | 2026-05-25T10:47:22.518Z WARN client/firewall/create_linux.go:55: failed to create native firewall: create firewall: no firewall manager found. Proceeding with userspace
netbird-proxy  | 2026-05-25T10:47:22.620Z INFO [account_id: d87vc5sda4bc73ft5ukg, service_key: domain:portfolio.pavanbhaskar.com] proxy/internal/roundtrip/netbird.go:403: notified management about tunnel connection
netbird-proxy  | 2026-05-25T10:47:23.005Z WARN [peer: 1pu593jwVFJWOxSbinhb5pzrsJSLf7l8Vs7Ren4fOEM=] client/internal/peer/worker_ice.go:164: ICE Agent is not initialized yet
netbird-proxy  | 2026-05-25T10:50:36.806Z WARN [http-server: https] ./caller_not_available:0: http: TLS handshake error from 103.217.239.66:54277: remote error: tls: unknown certificate
netbird-proxy  | 2026-05-25T11:01:38.835Z WARN [http-server: https] ./caller_not_available:0: http: TLS handshake error from 118.193.45.103:3946: EOF
netbird-proxy  | 2026-05-25T11:01:39.258Z WARN [http-server: https] ./caller_not_available:0: http: TLS handshake error from 118.193.45.103:3996: EOF
netbird-proxy  | 2026-05-25T11:01:39.688Z WARN [http-server: https] ./caller_not_available:0: http: TLS handshake error from 118.193.45.103:4038: EOF
netbird-proxy  | 2026-05-25T11:01:39.905Z WARN [http-server: https] ./caller_not_available:0: http: TLS handshake error from 118.193.45.103:4086: tls: client requested unsupported application protocols (["http/0.9" "http/1.0" "spdy/1" "spdy/2" "spdy/3" "h2c" "hq"])
netbird-proxy  | 2026-05-25T11:01:40.339Z WARN [http-server: https] ./caller_not_available:0: http: TLS handshake error from 118.193.45.103:4124: tls: client requested unsupported application protocols (["hq" "h2c" "spdy/3" "spdy/2" "spdy/1" "http/1.0" "http/0.9"])
netbird-proxy  | 2026-05-25T11:01:40.767Z WARN [http-server: https] ./caller_not_available:0: http: TLS handshake error from 118.193.45.103:4166: tls: client offered only unsupported versions: [302 301]
netbird-proxy  | 2026-05-25T11:01:41.411Z WARN [http-server: https] ./caller_not_available:0: http: TLS handshake error from 118.193.45.103:4204: EOF
netbird-proxy  | 2026-05-25T11:01:41.833Z WARN [http-server: https] ./caller_not_available:0: http: TLS handshake error from 118.193.45.103:4236: EOF
netbird-proxy  | 2026-05-25T11:01:42.042Z WARN [http-server: https] ./caller_not_available:0: http: TLS handshake error from 118.193.45.103:4274: tls: no cipher suite supported by both client and server; client offered: [16 33 67 c09e c0a2 9e 39 6b c09f c0a3 9f 45 be 88 c4 9a c008 c009 c023 c0ac c0ae c02b c00a c024 c0ad c0af c02c c072 c073 cca9 cc14 c007 c012 c013 c027 c02f c014 c028 c030 c060 c061 c076 c077 cca8 cc13 c011 a 2f 3c c09c c0a0 9c 35 3d c09d c0a1 9d 41 ba 84 c0 7 4 5]
netbird-proxy  | 2026-05-25T11:01:42.683Z WARN [http-server: https] ./caller_not_available:0: http: TLS handshake error from 118.193.45.103:4308: EOF
netbird-proxy  | 2026-05-25T11:01:43.098Z WARN [http-server: https] ./caller_not_available:0: http: TLS handshake error from 118.193.45.103:4372: EOF
netbird-proxy  | 2026-05-25T11:01:50.006Z WARN [http-server: https] ./caller_not_available:0: http: TLS handshake error from 118.193.45.103:5132: EOF
netbird-proxy  | 2026-05-25T11:10:40.315Z ERRO client/iface/wgproxy/bind/proxy.go:185: failed to read from remote conn: rels://netbird.akhilnaidu.com:443, use of closed network connection

I’ve experienced the same issue, but I gave up to make it work via netbird and I forced traefik to “pre-generate” the certificate to the domain I wanted. But it would be nice to know how to make it work.

Can you share me the process on how you pre-generated certificate with traefik!

Sure, it’s basically two additional lines in the command part at traefik service in your docker compose file. I guess for your case that would be:

- '--entrypoints.websecure.http.letsencrypt.domains[0].main=pavanbhaskar.com'
- '--entrypoints.websecure.http.letsencrypt.domains[0].sans=*.pavanbhaskar.com'

Thank you, with this process adding a new domain might require docker restart i guess!