WebSocket to different port

I have setup Traefik using docker and I'm trying to put my Network Video Recorder (Unifi NVR) behind reverse proxy. My current setup seems to work for the most part, except when the NVR tries to reach WebSocket through a different port. This is the docker compose for the Traefik setup:

traefik:
    image: traefik:2.3.2
    container_name: traefik
    environment:
      - NAMECHEAP_API_USER=${NAMECHEAP_API_USER}
      - NAMECHEAP_API_KEY=${NAMECHEAP_API_KEY}
    command:
    #### CLI commands that will configure Traefik (static) ####
      ## API Settings ##
      - --api.insecure=true # <== Enables insecure api
      - --api.dashboard=true # <== Enables the dashboard to view services, middlewares, routers, etc...
      - --api.debug=true # <== Enables additional endpoints for debugging and profiling
      ## Log Settings ##
      - --log.level=DEBUG # <== Setting the level of the logs
      ## Provider Settings ##
      - --providers.docker=true # <== Enables docker as a provider
      - --providers.docker.exposedbydefault=false # <== Don't expose every container to traefik, only expose enabled ones
      - --providers.file.filename=/etc/traefik/dynamic.yml # <== Reference to the dynamic configuration file
      ## Entrypoints Settings  ##
      - --entrypoints.web.address=:80 # <== Defining an entrypoint for port :80 named web
      - --entrypoints.web.http.redirections.entryPoint.to=websecure # <== Redirect web to websecure (http > https)
      - --entrypoints.web.http.redirections.entryPoint.scheme=https # <== Set HTTPS as redirection scheme
      - --entrypoints.websecure.address=:443 # <== Defining an entrypoint for https on port :443 named websecure
      ## Certificate Settings (Let's Encrypt) ##
      - --certificatesresolvers.default.acme.email=${EMAIL} # <== Setting email for certs
      - --certificatesresolvers.default.acme.storage=/etc/traefik/acme/acme.json # <== Defining acme file to store cert information
      - --certificatesresolvers.default.acme.dnschallenge=true # <== Enable DNS-01 ACME challenge  to generate and renew ACME certs
      - --certificatesresolvers.default.acme.dnschallenge.provider=namecheap # <== Set DNS-01 challenge provider
      - --certificatesresolvers.default.acme.dnschallenge.delayBeforeCheck=32 # <== Wait 32 seconds before checking propagation.
      - --certificatesresolvers.default.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53 # <== Use these to check for propagation.
      - --serversTransport.insecureSkipVerify=true # <== Disables SSL certificate verification between traefik and backend. Certificate does not need to be valid!
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ${DATADIR}/appdata/traefik/dynamic.yml:/etc/traefik/dynamic.yml
      - ${DATADIR}/appdata/traefik/acme.json:/etc/traefik/acme/acme.json
    labels:
      - "traefik.enable=true" # <== Enable traefik on itself to view dashboard and assign subdomain to view it
      - "traefik.http.routers.traefik.rule=Host(`traefik.example.com`)" # <== Setting the domain for the dashboard
      - "traefik.http.routers.traefik.service=api@internal" # <== Enabling the api to be a service to access
      - "traefik.http.routers.traefik.tls=true"
      - "traefik.http.routers.traefik.tls.certresolver=default"
      - "traefik.http.routers.traefik.tls.domains[0].main=example.com"
      - "traefik.http.routers.traefik.tls.domains[0].sans=*.example.com"
    ports:
      - 80:80
      - 443:443
      - 8080:8080
    restart: always

and this is the dynamic.yml I use to put non-docker services behind reverse proxy:

http:
  routers:
    nvr:
      rule: Host(`nvr.example.com`)
      service: nvr
      middlewares:
        - sslheader
        #- https-redirect
      tls: {}
  services:
    nvr:
      loadBalancer:
        servers:
          - url: "https://192.168.1.102:7443"
  middlewares:
    https-redirect:
      redirectScheme:
        scheme: https
        permanent: true
        port: "7443"
    sslheader:
      headers:
        customRequestHeaders:
          X-Forwarded-Proto: https

If I go to nvr.example.com I get to the web UI with valid certificate and all. But when I try to view a video stream from one of the cameras I get this error in browser console:
WebSocket connection to 'wss://nvr.example.com:7446/xxx' failed:

I assume the service tries to access WebSocket through different port than what is used to access the web UI (7446 vs 7443) and that is why it fails. How should I configure Traefik so accessing WebSocket over wss://nvr.example.com:7446 possible?

1 Like

I am having the same issue. If you find a solution please let me know.

You need to configure a new entrypoint that listens on port :7446, and add a new router that forwards to as service that listens on that port.

Could you provide an example of what you mean? I'm not sure I understand. Thanks.

Hello @shard,

An example would be to add another entrypoint listening on port 7446:

      ## Entrypoints Settings  ##
      - --entrypoints.web.address=:80 # <== Defining an entrypoint for port :80 named web
      - --entrypoints.web.http.redirections.entryPoint.to=websecure # <== Redirect web to websecure (http > https)
      - --entrypoints.web.http.redirections.entryPoint.scheme=https # <== Set HTTPS as redirection scheme
      - --entrypoints.websecure.address=:443 # <== Defining an entrypoint for https on port :443 named websecure
      - --entrypoints.video.address=:7446 # <== Defining an entrypoint for video on port :7446 named video

And then add a new router that forwards to a server that listens to that port:

http:
  routers:
    nvr:
      entrypoints:
        - "websecure"
      rule: Host(`nvr.example.com`)
      service: nvr
      middlewares:
        - sslheader
        #- https-redirect
      tls: {}
    nvrvideo:
      entrypoints:
        - "video"
      rule: Host(`nvr.example.com`)
      service: nvrvideo
      tls: {}
  services:
    nvr:
      loadBalancer:
        servers:
          - url: "https://192.168.1.102:7443"
    nvrvideo:
      loadBalancer:
        servers:
          - url: "https://192.168.1.102:7446"
  middlewares:
    https-redirect:
      redirectScheme:
        scheme: https
        permanent: true
        port: "7443"
    sslheader:
      headers:
        customRequestHeaders:
          X-Forwarded-Proto: https

I added those same changes to my files but I'm still getting that same error. I even added port mapping - 7446:7446 to the traefik container, but no luck.

I don't mean to revive an old thread, but I just wanted to leave a note here for people like me who may find this thread via a Google search, and avoid a DenverCoder9 situation! :stuck_out_tongue:

My configuration looks very similar to the snippets above, and after much Googling and finding this thread, I was finally able to solve the issue after adding...

  • The second entry point for secure WebSocket calls on 7446 (plus a redirected insecure one on 7445 for good measure)
  • The second service, redirected to http://192.168.x.y:~~7445 https://192.168.x.y:~~7446 for the WebSocket calls
    • I used the insecure port because I've been trial-and-erroring for like 3 hours now, and I don't want to break it by having Traefik cry about my UniFi Video controller not having a valid SSL cert - but I suppose disabling SSL validation on the route would probably work
    • EDIT: Changed it to use the secure port but with cert validation disabled - nothing exploded :slight_smile:
  • The middleware with the X-Forwarded-Proto header set to "https,wss" - again, I don't know if it needs to be set this way or not (found it in another Google result), but if it ain't broke, don't fix it...

I did all of the above, and was still getting "connection refused" errors, until I (after more time than I care to admit) did the last step... of exposing the 7445/6 ports in my compose config. :smiling_face_with_tear:

And if that still doesn't work, make sure you restart your container, and ensure your host has the extra ws/wss ports open! Hope this helps another poor soul!!