Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nginx WebSocket proxying keep getting HTTP 301 redirects

I have been trying to to get the Nginx WebSocket proxy to work for the past few days but for the life of me I couldn't get it to work. I followed the official guide here and have been using Python's websockets module as the server and a npm package wscat as the client. Direct connection from wscat to the Python WebSocket backend is working fine (so is the connection from Browser). But as soon as I layer in Nginx, it's not working and keep giving me a standard HTTP 301 redirect.

cURL debugging output with Nginx proxy:

$ curl 'http://test.ws:8080/websocket' \
> -H 'Pragma: no-cache' \
> -H 'Origin: http://localhost:8080' \
> -H 'Accept-Encoding: gzip, deflate, sdch' \
> -H 'Sec-WebSocket-Version: 13' \
> -H 'Sec-WebSocket-Key: V15bszpaQ+8Vq7mWR6NQbQ==' \
> -H 'User-Agent: Mozilla/5.0' \
> -H 'Upgrade: websocket' \
> -H 'Cache-Control: no-cache' \
> -H 'Connection: Upgrade' \
> -H 'Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits' \
    --compressed -v
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to test.ws (127.0.0.1) port 8080 (#0)
> GET /websocket HTTP/1.1
> Host: test.ws:8080
> Accept: */*
> Pragma: no-cache
> Origin: http://localhost:8080
> Accept-Encoding: gzip, deflate, sdch
> Sec-WebSocket-Version: 13
> Sec-WebSocket-Key: V15bszpaQ+8Vq7mWR6NQbQ==
> User-Agent: Mozilla/5.0
> Upgrade: websocket
> Cache-Control: no-cache
> Connection: Upgrade
> Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
> 
< HTTP/1.1 301 Moved Permanently
* Server nginx/1.8.0 is not blacklisted
< Server: nginx/1.8.0
< Date: Mon, 10 Aug 2015 15:04:26 GMT
< Content-Type: text/html
< Content-Length: 184
< Location: http://test.ws:8080/websocket/
< Connection: keep-alive
< 
<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.8.0</center>
</body>
</html>
* Connection #0 to host test.ws left intact

cURL debugging output without Nginx proxy:

$ curl 'http://test.ws:8765/' \
> -H 'Pragma: no-cache' \
> -H 'Origin: http://localhost:8080' \
> -H 'Accept-Encoding: gzip, deflate, sdch' \
> -H 'Sec-WebSocket-Version: 13' \
> -H 'Sec-WebSocket-Key: V15bszpaQ+8Vq7mWR6NQbQ==' \
> -H 'User-Agent: Mozilla/5.0' \
> -H 'Upgrade: websocket' \
> -H 'Cache-Control: no-cache' \
> -H 'Connection: Upgrade' \
> -H 'Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits' \
> --compressed -v
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to test.ws (127.0.0.1) port 8765 (#0)
> GET / HTTP/1.1
> Host: test.ws:8765
> Accept: */*
> Pragma: no-cache
> Origin: http://localhost:8080
> Accept-Encoding: gzip, deflate, sdch
> Sec-WebSocket-Version: 13
> Sec-WebSocket-Key: V15bszpaQ+8Vq7mWR6NQbQ==
> User-Agent: Mozilla/5.0
> Upgrade: websocket
> Cache-Control: no-cache
> Connection: Upgrade
> Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
> 
< HTTP/1.1 101 Switching Protocols
* Server Python/3.4 websockets/2.5 is not blacklisted
< Server: Python/3.4 websockets/2.5
< Upgrade: WebSocket
< Connection: Upgrade
< Sec-WebSocket-Accept: yR97tmHAm9KPEI5vfKiM0/sfTqQ=
^C

My Nginx (version 1.8.0) configuration is this (websockets running on test.ws (127.0.0.1) on port 8765) and nginx is listenning on port 8080 on localhost:

worker_processes  1;

error_log  logs/error.log debug;

events {
    worker_connections  1024;
}


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

    sendfile        on;
    keepalive_timeout  65;

    upstream ws {
        server 127.0.0.1:8765;
    }

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

    server {
        listen       8080;
        server_name  test.ws;

        access_log  logs/localhost.access.log;

        location / {
            root   html;
            index  index.html index.htm;
        }

        location /websocket/ {
            proxy_pass http://ws;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }

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

}

include servers/*;

Anyone know what did I do wrong here and why is it giving me 301 redirect?

like image 564
Devy Avatar asked Aug 10 '15 15:08

Devy


2 Answers

In my case, it came from the client using path /websocket instead of /websocket/ thereby preventing the location rule to kick in. But the impact of locations order was a good hint!

like image 179
PeeWee2201 Avatar answered Sep 22 '22 04:09

PeeWee2201


As it turns out, the root location stanza in my Nginx configuration has been interfering with the WebSocket proxy pass tunnel. There are three ways to solve this problem.

  1. Modify the root location stanza to match exactly.

    location = / {
        root   html;
        index  index.html index.htm;
    }
    
  2. Re-order the root location stanza to be after the websocket location stanza (specific ones first, general ones last, just like how rewrite matching rule works).

  3. Removing the following root location stanza in the Nginx configuration will make it work.

    location / {
        root   html;
        index  index.html index.htm;
    }
    
like image 32
Devy Avatar answered Sep 18 '22 04:09

Devy