Reverse proxying HTTP and WebSockets with virtual hosts using nginx and tcp_proxy_module

5 October 2012

I spent today trying to work out how we could get PythonAnywhere to support WebSockets in our users’ web applications. This is a brief summary of what I found, I’ll put it in a proper post on the PythonAnywhere blog sometime soon…

We use nginx, and it can happily route HTTP requests through to uwsgi applications (which is the way we use it) and can even more happily route them through to other socket-based servers running on specific ports (which we don’t use but will in the future so that we can support Twisted, Tornado, and so on — once we’ve got network namespacing sorted).

But by default, nginx does not support reverse proxying WebSockets requests. There are various solutions to this posted around the net, but they don’t explain how to get it working with virtual hosts. I think that this is because they’re all a bit old, because it’s actually quite easy once you know how.

(It’s worth mentioning that there are lots of cool non-nginx solutions using excellent stuff like haproxy and hipache. I’d really like to upgrade our infrastructure to use one of those two. But not now, we all too recently moved from Apache to nginx and I’m scared of big infrastructure changes in the short term. Lots of small ones, that’s the way forward…)

Anyway, let’s cut to the chase. This excellent blog post by Johnathan Leppert explains how to configure nginx to do TCP proxying. TCP proxying is enough to get WebSockets working if you don’t care about virtual hosts — but because arbitrary TCP connections don’t necessarily have a Host: header, it can’t work if you do care about them.

However, since the post was written, the nginx plugin module Johnathan uses has been improved so that it now supports WebSocket proxying with virtual hosts.

To get nginx to successfully reverse-proxy WebSockets with virtual host support, compile Nginx with tcp_proxy_module as per Johnathan’s instructions (I’ve bumped the version to the latest stable as of today):

export NGINX_VERSION=1.2.4
curl -O http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz
git clone https://github.com/yaoweibin/nginx_tcp_proxy_module.git
tar -xvzf nginx-$NGINX_VERSION.tar.gz
cd nginx-$NGINX_VERSION
patch -p1 < ../nginx_tcp_proxy_module/tcp.patch
./configure --add-module=../nginx_tcp_proxy_module/
sudo make && make install

Then, to use the new WebSockets support in tcp_proxy_module, put something like this in your nginx config:

worker_processes  1;

events {
    worker_connections  1024;
}

tcp {
    upstream site1 {
        server 127.0.0.1:1001;

        check interval=3000 rise=2 fall=5 timeout=1000;
    }

    server {
        listen 0.0.0.0:80;
        server_name site1.com;

        tcp_nodelay on;
        websocket_pass site1;
    }
}


tcp {
    upstream site2 {
        server 127.0.0.1:1002;

        check interval=3000 rise=2 fall=5 timeout=1000;
    }

    server {
        listen 0.0.0.0:80;
        server_name site2.com;

        tcp_nodelay on;
        websocket_pass site2;
    }
}

Hopefully that's enough to help a few people googling around for help like I was this morning. Leave a comment if you have any questions!