1

I'm using the vaultwarden docker container, which basically requires a reverse proxy to provide SSL.

The container runs a separate web server for Websockets, because Rust's rocket doesn't support web sockets on the same port.

The instructions for VaultWarden say:

  • Route the /notifications/hub endpoint to the WebSocket server, by default at port 3012, making sure to pass the Connection and Upgrade headers
  • Route everything else, including /notifications/hub/negotiate, to the standard Rocket server, by default at port 80

How would I configure my nginx reverse proxy to support this setup?

My config looks like this:

docker-compose.yml

version: '3'
services:
    vaultwarden:
      image: vaultwarden/server:1.25.2
      volumes:
        - /srv/vaultwarden/vaultwarden:/data/
      restart: always
      environment:
        - WEBSOCKET_ENABLED=true

    nginx:
      image: nginx:1.23.1
      volumes:
        - /srv/vaultwarden/nginx/templates:/etc/nginx/templates
        - /srv/vaultwarden/nginx/ssl:/etc/nginx/ssl
      ports:
        - "443:443"
      environment:
        - NGINX_PORT=443

nginx:

server {
    listen       ${NGINX_PORT} ssl http2 default;
    server_name  _;

    # SSL
    ssl_certificate /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;

    # Web sockets
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    location / {
        proxy_pass http://vaultwarden;
    }
}

Should I be:

  • Exposing port 3012 on the vaultwarden container directly?
    • What would the nginx config look like for that?
  • Exposing port 3012 on the nginx container, and proxy_passing it to the vaultwarden container?
    • What would that look like?

2 Answers 2

1

After some digging, this is what I found:

If you pass the WEBSOCKET_ENABLED=true environment variable to the vaultwarden container as I do above in my docker-compose.yml file, the container will start 2 servers:

  1. Web server on port 80
  2. Websocket server on port 3012

Nothing on the client side says anything about connecting to port 3012. The client tries to set up a websocket on /notifications/hub. Same port as your regular web server.

You do NOT need to expose port 3012 on either container in your docker-compose.yml file. All you need is this in your nginx config file:

location = /notifications/hub {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header Host $host;

    proxy_pass http://vaultwarden:3012;
}

This will transparently have nginx redirect all requests to /notifications/hub to port 3012 on the vaultwarden container.

Since both nginx and vaultwarden are in the same docker-compose.yml file, they are on the same internal network, and the nginx container already has access to all ports on the vaultwarden container. You don't need to allow this port anywhere.

The client isn't aware that internally, web sockets are using a different port. The port translation all happens server-side.

1

I would suggest exposing port 3012 in nginx:

    nginx:
      image: nginx:1.23.1
      volumes:
        - /srv/vaultwarden/nginx/templates:/etc/nginx/templates
        - /srv/vaultwarden/nginx/ssl:/etc/nginx/ssl
      ports:
        - "443:443"
        - "3012:3012"
      environment:
        - NGINX_PORT=443

Proxy passing for WebSockets on nginx can be a bit tricky. I once had struggled to set it up correctly (see this post) -- I also had a custom URL/endpoint for WebSockets there.

Generally, the options are a few options how WS proxy can work: (1) you have custom endpoint for WebSocket connections, or (2) WS goes through the root /, but at a different port than HTTP, or (3) it works more like for socket.io (i.e., each endpoint/URL being also a separate endpoint for WS connection). The documentation says:

Route the /notifications/hub endpoint to the WebSocket server, by default at port 3012, making sure to pass the Connection and Upgrade headers

Route everything else, including /notifications/hub/negotiate, to the standard Rocket server, by default at port 80

So I guess it would have to be something like that in the nginx config:

       # WebSocket support
       location ~ ^/notifications/hub/ {
           proxy_pass http://vaultwarden:3012;
           proxy_http_version 1.1;
           proxy_set_header Upgrade $http_upgrade;
           proxy_set_header Connection "Upgrade";
           proxy_set_header Host $host;
       }

       # HTTP proxy
       location / {
           proxy_pass http://vaultwarden;
       }

But it requires some testing.

I hope that helps you in some way.

3
  • If I'm exposing port 3012 on nginx, then I'm expecting the browser to connect to that port. But the nginx config isn't configured to listen on that port. The location / section above is in the listen 443 server section. If I put the web socket section, as you've shown above, right next to it, it will also be under the listen 443 section, and nothing on the nginx container will actually listen to connections on port 3012. That doesn't sound right. I tried it, but it didn't work. I think I'm missing a new server section with listen 3012, but what goes under it?
    – John
    Commented Aug 30, 2022 at 19:06
  • I was wrong. port 3012 doesn't need to be exposed externally, as shown in my answer. I'm not super clear why you chose to use location ~ ^/[0-9]+/notifications/hub/. Why try to match one or more digits? Where are those digits coming from?
    – John
    Commented Aug 31, 2022 at 14:34
  • Sorry, these digits came from one of my examples. I edited my answer. Ok, it makes sense that 3012 can be handled by the nginx. So, your answer is right. I hope I helped a bit in getting to this answer, cheers. Commented Aug 31, 2022 at 15:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .