Using nginx to host websockets and http on the same domain:port

The problem

Tom van Neerijnen
localhost.run

--

Often websockets and http for a service are handled in different apps. There are very good reasons for this. Python’s flask for example is brilliant because of it’s simplicity, but that simplicity is in part because HTTP is request response in nature, it can receive a request, craft a response, and then move on to the next request.

websockets however aren’t that simple. They’re an always open stream. Sure, you can handle this with threads or by moving to an asynchronous framework, but if this isn’t a pattern you’re already comfortable with you risk making your app less stable.

But if you do decide to run them as separate apps how do you deal the issues that will come with that, like cross domain browser security if you run them on different domains, or different ports if you run them on the same domain?

Enter nginx

Nginx will let you receive both your HTTP and websocket traffic on a single domain and port, and then forward each one on to the correct app. It can do this because websockets starts out as a regular HTTP GET request.

GET /ws HTTP/1.1
Host: abcd.localhost.run
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://abcd.localhost.run

I won’t tell you how to install nginx, there are plenty of blog posts that describe this far better than I could, but I will give you the nginx config to enable the HTTP/websocket split.

By default it will listen on port 8000, HTTP will go to localhost:8080 and websockets to localhost:8081, and you will need to set your websocket url in your app to be ws://{your domain:your port}/ws/ (or wss://… if you’re on https).

events {
worker_connections 512;
}
http {
server {
listen 8000;
location /ws/ {
proxy_pass http://localhost:8081;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
location / {
proxy_pass http://localhost:8080;
}
}
}

You can try this with an internet accessible domain for free now with localhost.run. Runn ssh -R localhost:8000 ssh.localhost.run in a terminal, set your domain names for your ws(s):// url to be the returned localhost.run one, start nginx and your apps and connect to your localhost.run url.

--

--