201

I'm so lost and new to building NGINX on my own but I want to be able to enable secure websockets without having an additional layer.

I don't want to enable SSL on the websocket server itself but instead I want to use NGINX to add an SSL layer to the whole thing.

Every web page out there says I can't do it, but I know I can! Thanks to whoever (myself) can show me how!

crockpotveggies
  • 12,682
  • 12
  • 70
  • 140

8 Answers8

281

Just to note that nginx has now support for Websockets on the release 1.3.13. Example of use:

location /websocket/ {

    proxy_pass ​http://backend_host;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400;

}

You can also check the nginx changelog and the WebSocket proxying documentation.

Tarantula
  • 19,031
  • 12
  • 54
  • 71
  • It has the same timeout issues as expressed above ;) – 3rdEden Apr 04 '13 at 11:45
  • 8
    @3rdEden: For timeout issues, `proxy_read_timeout` works, I edited the answer. – Steve Kehlet Apr 29 '13 at 20:02
  • 3
    Where should I put this configuration and what is backend_host ? – Aysennoussi May 21 '14 at 21:08
  • 4
    @Sekai: A `location` directive is put within a `server` or another `location` directive (see [location docs](http://nginx.org/en/docs/http/ngx_http_core_module.html#location)). `backend_host` is an `upstream` (see [upstream docs](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream)) - one or a group of servers that you'll proxy to. – Radko Dinev Aug 13 '14 at 18:37
  • @RadkoDinev -- is upstream necessary, or if I just have one server, I can just use that in the proxy_pass directive? – jlee Aug 23 '16 at 04:03
  • @jlee As stated in the [documentation of the `proxy_pass` directive](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) it's value is an URL in general. Having one server, you could just use it's DNS name or IP address as a value. `upstream` is usually used for more than one server, and/or when you want to specify additional options (e.g. [`keepalive`](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive)). – Radko Dinev Aug 23 '16 at 10:47
  • 1
    What about this timeout problem? Do we have really to set it to a very large number to avoid it? Isn't there now any more elegant solution? – Mohammed Noureldin Jan 04 '18 at 23:48
  • Have you figured out the solution? Since I had to also faced a similar problem https://stackoverflow.com/q/53411060/7713811 – Nɪsʜᴀɴᴛʜ ॐ Nov 22 '18 at 08:26
  • Worked for me. I ran into this problem when deploying a Blazor Server app to Azure Container Instances after following this document: https://learn.microsoft.com/en-us/azure/container-instances/container-instances-container-group-ssl – Neurion Jan 18 '20 at 04:48
  • If we're using https why are we writing `"proxy_http_version"` and `"proxy_set_header Upgrade $http_upgrade"`? – Normajean Jun 05 '21 at 02:21
  • Trying hard for weeks without any working solutions :-( https://stackoverflow.com/questions/76593975/esp32-websocket-or-socketio-to-node-red-throught-nginx-ssl – Jean-Philippe Encausse Jul 01 '23 at 09:31
82

This worked for me:

location / {
    # redirect all HTTP traffic to localhost:8080
    proxy_pass http://localhost:8080;
    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";
}

-- borrowed from: https://github.com/nicokaiser/nginx-websocket-proxy/blob/df67cd92f71bfcb513b343beaa89cb33ab09fb05/simple-wss.conf

Harlan T Wood
  • 2,064
  • 21
  • 19
59

Have no fear, because a brave group of Ops Programmers have solved the situation with a brand spanking new nginx_tcp_proxy_module

Written in August 2012, so if you are from the future you should do your homework.

Prerequisites

Assumes you are using CentOS:

  • Remove current instance of NGINX (suggest using dev server for this)
  • If possible, save your old NGINX config files so you can re-use them (that includes your init.d/nginx script)
  • yum install pcre pcre-devel openssl openssl-devel and any other necessary libs for building NGINX
  • Get the nginx_tcp_proxy_module from GitHub here https://github.com/yaoweibin/nginx_tcp_proxy_module and remember the folder where you placed it (make sure it is not zipped)

Build Your New NGINX

Again, assumes CentOS:

  • cd /usr/local/
  • wget 'http://nginx.org/download/nginx-1.2.1.tar.gz'
  • tar -xzvf nginx-1.2.1.tar.gz
  • cd nginx-1.2.1/
  • patch -p1 < /path/to/nginx_tcp_proxy_module/tcp.patch
  • ./configure --add-module=/path/to/nginx_tcp_proxy_module --with-http_ssl_module (you can add more modules if you need them)
  • make
  • make install

Optional:

  • sudo /sbin/chkconfig nginx on

Set Up Nginx

Remember to copy over your old configuration files first if you want to re-use them.

Important: you will need to create a tcp {} directive at the highest level in your conf. Make sure it is not inside your http {} directive.

The example config below shows a single upstream websocket server, and two proxies for both SSL and Non-SSL.

tcp {
    upstream websockets {
        ## webbit websocket server in background
        server 127.0.0.1:5501;
        
        ## server 127.0.0.1:5502; ## add another server if you like!

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

    server {
        server_name _;
        listen 7070;

        timeout 43200000;
        websocket_connect_timeout 43200000;
        proxy_connect_timeout 43200000;

        so_keepalive on;
        tcp_nodelay on;

        websocket_pass websockets;
        websocket_buffer 1k;
    }

    server {
        server_name _;
        listen 7080;

        ssl on;
        ssl_certificate      /path/to/cert.pem;
        ssl_certificate_key  /path/to/key.key;

        timeout 43200000;
        websocket_connect_timeout 43200000;
        proxy_connect_timeout 43200000;

        so_keepalive on;
        tcp_nodelay on;

        websocket_pass websockets;
        websocket_buffer 1k;
    }
}
Community
  • 1
  • 1
crockpotveggies
  • 12,682
  • 12
  • 70
  • 140
  • 6
    This was pretty helpful, but I was still getting timeouts at 60secs. I managed to fix this by setting the following: timeout 43200000; websocket_connect_timeout 43200000; websocket_read_timeout 43200000; websocket_send_timeout 43200000; proxy_connect_timeout 43200000; proxy_read_timeout 43200000; proxy_send_timeout 43200000; – jbg Sep 02 '12 at 06:18
  • 1
    I wanted to serve websockets off the same http port and only after the browser had been authenticated. It looks like this can't handle websockets on the same port. How do people handle this? – uroc Nov 15 '12 at 19:06
  • 1
    It will take some software modification to detect the incoming protocol. Since websockets actually start as an HTTP handshake (a higher software level than TCP) you have to tweak your app to handle both TCP and HTTP traffic. I can't recommend a way to do this just yet. – crockpotveggies Nov 15 '12 at 21:20
  • 3
    In case other folks from 2018 come here, these directives don't work any more. Go to http://nginx.org/en/docs/http/websocket.html for recent instructions or see Harlan T Wood's answer below. – GaryO Jun 25 '18 at 21:01
  • Have you figured out the solution? Since I had to also faced a similar problem https://stackoverflow.com/q/53411060/7713811 – Nɪsʜᴀɴᴛʜ ॐ Nov 22 '18 at 08:24
26

for .net core 2.0 Nginx with SSL

location / {
    # redirect all HTTP traffic to localhost:8080
    proxy_pass http://localhost:8080;
    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 $http_connection;
}

This worked for me

Thomas Fritsch
  • 9,639
  • 33
  • 37
  • 49
13

For me, it came down to the proxy_pass location setting. I needed to change over to using the HTTPS protocol, and have a valid SSL certificate set up on the node server side of things. That way when I introduce an external node server, I only have to change the IP and everything else remains the same config.

I hope this helps someone along the way... I was staring at the problem the whole time... sigh...

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
upstream nodeserver {
        server 127.0.0.1:8080;
}
server {
        listen 443 default_server ssl http2;
        listen [::]:443 default_server ssl http2 ipv6only=on;
        server_name mysite.com;
        ssl_certificate ssl/site.crt;
        ssl_certificate_key ssl/site.key;
        location /websocket { #replace /websocket with the path required by your application
                proxy_pass https://nodeserver;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection $connection_upgrade;
                proxy_http_version 1.1;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                proxy_intercept_errors on;
                proxy_redirect off;
                proxy_cache_bypass $http_upgrade;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-NginX-Proxy true;
                proxy_ssl_session_reuse off;
            }
}
Sean M
  • 171
  • 1
  • 7
  • 1
    I tried `localtion /horizon`, but it's not working. Only `localtion /` or `location /websockify` works. Don't know why... – njuguoyi Oct 27 '18 at 10:11
  • works if replace "proxy_set_header Connection $connection_upgrade;" with "proxy_set_header Connection $http_connection;" – Xie Yanbo Sep 01 '22 at 02:29
9

A good, concise article by Pankaj Malhotra discusses how to do this with NGINX and is available here.

The basic NGINX configuration is reproduced below:

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

upstream appserver {
    server 192.168.100.10:9222; # appserver_ip:ws_port
}

server {
    listen 8888; // client_wss_port

    ssl on;
    ssl_certificate /path/to/crt;
    ssl_certificate_key /path/to/key;


    location / {
        proxy_pass http://appserver;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}
Tommy Dorsey
  • 75
  • 1
  • 9
btiernay
  • 7,873
  • 5
  • 42
  • 48
9

Using nginx/1.14.0

i have a websocket-server running on port 8097 and users connect from to wss on port 8098, nginx just decrypts the content and forwards it to the websocket server

So i have this config file (in my case /etc/nginx/conf.d/default.conf)

server {
    listen   8098;
        ssl on;
        ssl_certificate      /etc/ssl/certs/domain.crt;
        ssl_certificate_key  /root/domain.key;
    location / {

        proxy_pass http://hostname:8097;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 86400;

    }
}
john Smith
  • 17,409
  • 11
  • 76
  • 117
  • 2
    In 2022 this helped me a lot. Nothing worked for WSS/WS until I added the read timeout. – gwhiz Sep 13 '22 at 20:31
4

If you want to add SSL in your test environment, then you can use mkcert. Below I mentioned the GitHub URL.
https://github.com/FiloSottile/mkcert
And also below I mentioned sample nginx configuration for reverse proxy.

server {
        listen 80;
        server_name test.local;
        return 301 https://test.local$request_uri;
}
server {
  listen 443 ssl;
  server_name test.local;
  ssl_certificate /etc/nginx/ssl/test.local.pem;
  ssl_certificate_key /etc/nginx/ssl/test.local-key.pem;

   location / {
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $remote_addr;
      proxy_set_header X-Client-Verify SUCCESS;
      proxy_set_header Host $http_host;
      proxy_set_header X-NginX-Proxy true;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
      proxy_pass http://localhost:3000;
      proxy_redirect off;
      proxy_buffering off;
    }
}