Search code examples
nginxsslwebsocketnginx-reverse-proxysurrealdb

How to connect to a secure WebSocket SurrealDB instance over HTTPS via nginx?


I'm trying to solve this problem I've been experiencing wherein I've been unable to connect to my SurrealDB backend over HTTPS/WSS. When attempting to connect via HTTP or WS, things work just fine, but adding any sort of SSL seems to break everything. My current stack looks like this:

  • nginx - For serving my web content and providing a reverse proxy to the SurrealDB instance on its default port (:8000).
  • SurrealDB - The database solution I am using for my site (perhaps one of my favorite db solutions of all time... when it works 😋)
  • React with Next.js - The mostly front-end solution for my website.
  • Cloudflare - For DNS routing and proxying.

My nginx configuration looks like this:

worker_processes 1;

events {
    worker_connections 1024;
}


http {
    include mime.types;
    default_type application/octet-stream;

    map $http_upgrade $connection_upgrade {
        default upgrade;
        '' close;
    }

    upstream websocket {
        server 127.0.0.1:4321;
    }

    sendfile on;

    keepalive_timeout 65;

    # HTTP server
    server {
        listen 443 ssl;
        server_name [...].com;

        ssl_certificate C:\Certbot\live\[...].com\fullchain.pem;
        ssl_certificate_key C:\Certbot\live\[...].com\privkey.pem;

        location / {
            proxy_pass http://127.0.0.1:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
            root html;
        }
    }

    # Secure WebWocket (WSS) server
    server {
        listen 443;
        server_name dev.db.[...].com;


        ssl_certificate C:\Certbot\live\[...].com\fullchain.pem;
        ssl_certificate_key C:\Certbot\live\[...].com\privkey.pem;

        location / {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;

            proxy_http_version 1.1;
            proxy_pass http://websocket;
            proxy_redirect off;
            proxy_pass_request_headers on;
        }
    }
}

Cloudflare is setup to forward traffic to the main domain and all subdomains to the same server (everything is currently hosted from the same machine.) Both configuration options (A records) are set to the same IP address and are proxied.

The proxy to port 3000 works and the Next.js instance is accessible via HTTPS, so I don't imagine that nginx itself is messed up, but I might be wrong.

I have tried various solutions to this problem, including the following:

  • Issuing separate SSL certificates for the subdomain and the primary domain (using certbot),

  • Sharing the same SSL certificate for both domains (using certbot),

  • Pointing proxy-pass to an upstream as well as a direct 127.0.0.1:8000 approach,

  • Ensuring the following:

    • proxy_http_version is set to 1.1.
    • proxy_set_header Upgrade is set to $http_upgrade or upgrade.
  • Listening on both 443 and 80, including and excluding the ssl suffix on the listen directive.

  • Changing the server_name from dev.db.[...].com to various other options, with no success,

  • Port-forwarding port 8000, 443, 80, and 8080.

  • Adding firewall rules (TCP/UDP + incoming/outgoing) for the aforementioned ports, as well as port 3000 (for the Next.js instance).

  • Changing SurrealDB's address and port binding (--bind) to something else. Note that the --addr flag available when starting SurrealDB is something I have not yet tried as I cannot quite seem to figure out the proper way to manipulate it. Could this be the issue?

  • Attempted connection via local network and external WSS port testing tool, both failures,

  • Reordered nginx configuration to use a location /ws instead of a server directive just to see if that might work (unfortunately it did not).

This is the error I get every single time I attempt to connect from any medium:

Console log error "WebSocket connection to wss://dev.db.censored.com/' failed

Are there any suggestions? Am I perhaps missing something, or maybe I did this incorrectly?

(Note that the [...] in the examples provided is the censored domain name, not literal characters.)


Solution

  • I solved my own problem! The issue was that I was not proxying the /rpc endpoint properly in my nginx.conf. The fix was to add this to my default server entry in the configuration:

    location /rpc {
        proxy_pass http://127.0.0.1:8000;
    
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    
        # WebSocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
    

    This allows the JavaScript library to access the correct SurrealDB endpoint and fixed my problem right away. In hindsight, I feel like it should have been obvious, but oh well! If anyone else has a similar problem getting SurrealDB to work with your nginx configuration, I hope this helps!