Websocket not proxied, client receives response code 200

I’ve been trying to give Cloudflare a try by proxying a personal site, but I can’t seem to get any websocket connections proxied.

Setup: client =[wss]=> CF =[??]=> NGINX =[unix socket]=> Node.JS

The NGINX server is using a Letsencrypt certificate, CF its own.

Running a simple test:

wscat --connect wss://{{sitename}}.com/formlistener
error: Unexpected server response: 200

Both Chrome and Firefox show the same response code in the reply.

As soon as I change the DNS setting from Proxied to DNS only websocket connections work as expected.

The relevant NGINX config:

location = /formlistener {
    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 Host $host;
    proxy_set_header X-NginX-Proxy true;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    proxy_pass http://unix:/tmp/formlistener.sock;
    proxy_redirect off;
    proxy_http_version 1.1;

    # These I added to test CF. DNS-only works with or without them.
    proxy_ssl_session_reuse off;
    proxy_cache_bypass $http_upgrade;
}

The WebSockets option in the Network tab is enabled. It’s a free-tier account (for now), but according to the documentation that shouldn’t matter, right?

Can anyone please help me sort this out so I can give CF a proper try?

1 Like

Okay, let’s go step by step as I see at your Nginx it could be even the start.

What about HTTPS?
Are you using it over HTTPS on your domain?
Do you have Flexible SSL option enabled or some other on SSL/TLS tab at the Cloudflare dashboard?

May I ask are you using it over 443 port or some other (http { listen 80 ... listen 443 ...}), hopefully compatible and supported by Cloudflare (:orange: cloud - proxied)?

In your Node.JS app, have you specified the usage of wss URLs?
Make sure at Cloudflare dashboard → Network → WebSockets the option is turned on (enabled).

If you want use WSS (websocket over TLS) use HTTPS connection.

wss://yourdomain.com:443 (HTTPS connection)

Next one, it should be:

location /formlistener/ { ... } # no = symbol and add the ending slash /

Second one:

May I ask is the /tmp/ directory or formlistener.sock writable, or for testing purpose, set to CHMOD 777 in that way?

What about adding location /wss/ {} or location /socket.io/ {} depending on the app type to the Nginx conf or vhost file?

Do not forget to add this in Nginx.conf file:

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

Try with below example in the domain.vhost file:

client_max_body_size 100M;

location /formlistener/ {
    proxy_set_header Host $http_host;
    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_pass http://127.0.0.1:8000/; # or your unix:
    proxy_redirect off;
    proxy_buffering off;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Host $server_name;
    proxy_set_header SCRIPT_NAME /formlistener/; # maybe not needed, but ...
}

location /wss/ { # or it should be /ws/, depending how you use it ...
    proxy_pass http://127.0.0.1:8000;  #or the same as above your unix: , maybe also here with a slash at the end /
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_redirect off;
    proxy_set_header Host $http_host;
}

location /assets/  {
    alias /path/to/assets;
}

Are you using something like below?:

# server
const WebSocketServer = require('ws').Server;
const wss = new WebSocketServer({
    server: server // express server object, running on port 8085 or some other
});

# browser
var wss = new WebSocket("wss://domain.name");

# or maybe this?

const WebSocket = require('ws');
const ws = new WebSocket('ws://put_your_ip_or_socket_here');

What about subscribe command?
Are you sending it?

Nevertheless, just to re-check, make sure Cloudflare is allowed in Nginx:

IP list here:

Also, if needed, the real visitor IP:

Thanks for the detailed write-up! Let me address the points one by one below:

Everything TCP uses HTTPS, so everything besides the local unix domain between NGINX and Node.JS, which is a setup that works just fine without CF.

It’s set to Full.

NGINX is set to redirect all traffic to the standard SSL (443) port, websockets included.

Yes, in the client-side script it’s instantiating a new WebSocket with url new WebSocket(wss://${window.location.hostname}/formlistener) (don’t mind the missing quotes, they just don’t seem to work in Markdown here, the URL works fine without CF proxy).

Yes, made sure that was turned on. Also tried re-enabling it and most protocol permutations in that tab.

According to the NGINX access logs nothing comes in. I made the change as proposed (with the trailing slash) in NGINX and the client-side script. Same result: Works without CF, but not for a proxied site.

Both share the same group and permissions are set to allow full access (chmod x7x).

Is this required even though the location would never be accessed?

Tried, same outcome.

The CF IPs have no issue accessing the origin server, no IPs are blacklisted. All regular HTML traffic works fine. It’s just websocket traffic that doesn’t arrive at all to the origin server.

One reason I posted here is that this issue (CF sending a ‘200’ when trying to establish a WebSocket connection) matches the issues people had when CF didn’t support websocket connections on free-tier accounts yet.

Would recommend to go with Full (Strict) if possible:

Actually, it is being accessed :slight_smile: (check the develoepr console for any errors)
As from my experience, yes as far as Nginx wouldn’t know what to do about /wss/ path and throw 403/404 or even some 50x error - I saw some errors in console at first, therefore added location block /ws/ (as my app was running over HTTP that time) and work fine (due to app is doing transform from http:// to ws://, or https:// to wss:)

1 Like

Not according the NGINX logs. Not even after adding the routes as proposed.

I did, of course:

Firefox can’t establish a connection to the server at wss://{{sitename}}.com/formlistener/.

and

wscat --connect wss://{{sitename}}.com/wss/
error: Unexpected server response: 200

Never ever needed to make that route with NGINX directly connected to the web. I added the paths regardless, just to try and no proxying there either.

No URL transformations are done in this setup.

With the above nginx vhost file setup, it’s working at my end.

See in action here - even this example is using Django Python, but Web Sockets and Nginx, while using HTTP only (not recommended) just for testing purposes as the backend is running over HTTP only, meaning therefore the “101 switching protocol” would have to be seen in the Network tab:

  • http://django.northsrv.com/chat

When you enter chat room name, and therefore send an text example message, you would see it in at the Network tab as ws://django.northsrv.com/ws/chat/test/.

ws://django.northsrv.com/ws/chat/test/
--
Upgrade: WebSocket
Server: nginx
Connection: upgrade

In the running process log from the server console, I see WS socket success 200 for each received message.

If so, maybe there is something to need to setup at the NodeJS app? :thinking:

It works even when I turn just for testing purposes what does the app do when HTTP->HTTPS and vice-versa the Flexible SSL option (not recommended) and proxied mode (:orange: cloud). Works fine with Full (Strict) SSL option too (only need to add the Cloudflare Origin CA certificate to the Nginx vhost, while not changing anything except ws:// to wss:// in the app and location /ws/ to location /wss/ in Nginx vhost file).

Maybe, you need to change in Nginx vhost file to the location / (naked domain) instead of /formlistener/ (app URLs), as if the app is working on /formlistener/ as like mine (defined to work under /chat/)?

Maybe someone else has some more experience and some idea, kindly and patiently wait for another reply :wink:

@fritex Thanks for your effort put in. It turned out to be a combination of me ‘pressing the buttons’ & Cloudflare. It was not an NGINX issue. Note that it works now without the extra ws or wss locations, so you can probably remove yours as well (and replace the ‘SCRIPT’ header with an URL path and separate upstreams).

For anyone reading this having the same issue:

It turned out to be a partially active ‘Pages’ setup. Initially I wanted to see how well ‘Pages’ worked, so I uploaded a static version of the site. Later I removed the CNAME records and set the DNS back to what it should be for proxying the origin server. This worked fine for the normal website stuff, but as it turns out not for WebSockets. After removing the Pages account for the site the websockets started working as expected.

1 Like

This topic was automatically closed 3 days after the last reply. New replies are no longer allowed.