Reverse proxy to websocket always fails

1. The problem I’m having:

I’m trying to establish a websocket connection, but it keeps failing.
In the client I have:
const socket = new WebSocket('wss://doekewartena.nl/wss59fps');

In the node js server I have:
const wss = new WebSocket.Server({ server, path: '/wss59fps' });

My Caddyfile:

doekewartena.nl {

        log {
                output file /var/log/caddy/frustration.log
        }

        reverse_proxy /wss59fps localhost:9090

        redir https://www.{host}{uri}
}

www.doekewartena.nl {
        root * /home/doekewartena_nl/sftp/www
     
        #
        # main
        #

        handle_path /* {
                rewrite * /main{uri}
                file_server
        }

        #
        # 59 fps
        #

        redir /59fps    /59fps/

        handle_path /59fps/* {
                rewrite * /59fps/client{uri}
                file_server
        }
}

I get a 302 status which will be listed below in #2.
At this point I’m just lost of what to do. And since most topics are locked it’s hard to ask questions of why someone did what.

The thing that makes the most sense to me is in this topic:

@websockets {
	header Connection *Upgrade*
	header Upgrade websocket
}
reverse_proxy @websockets localhost:6001

I don’t understand how to intergrate this in my Caddyfile tho.
Like how do I combine the above snippet with the wss59fps path?

2. Error messages and/or full log output:

sudo cat /var/log/caddy/frustration.log
{"level":"info","ts":1725298657.6525447,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"192.168.1.254","remote_port":"54087","client_ip":"192.168.1.254","proto":"HTTP/1.1","method":"GET","host":"doekewartena.nl","uri":"/wss59fps","headers":{"Sec-Websocket-Version":["13"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["https://www.doekewartena.nl"],"Sec-Websocket-Key":["uChH+3RCdjMcJdTlC1spFg=="],"Pragma":["no-cache"],"Accept":["*/*"],"Sec-Websocket-Extensions":["permessage-deflate"],"Sec-Fetch-Mode":["websocket"],"Upgrade":["websocket"],"Accept-Language":["en-GB,en;q=0.5"],"Connection":["keep-alive, Upgrade"],"Cache-Control":["no-cache"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"http/1.1","server_name":"doekewartena.nl"}},"bytes_read":0,"user_id":"","duration":0.000183488,"size":0,"status":302,"resp_headers":{"Server":["Caddy"],"Alt-Svc":["h3=\":443\"; ma=2592000"],"Location":["https://www.doekewartena.nl/wss59fps"],"Content-Type":[]}}
{"level":"info","ts":1725298868.9810967,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"192.168.1.254","remote_port":"64115","client_ip":"192.168.1.254","proto":"HTTP/1.1","method":"GET","host":"doekewartena.nl","uri":"/wss59fps","headers":{"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Websocket-Extensions":["permessage-deflate"],"Sec-Fetch-Dest":["empty"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:129.0) Gecko/20100101 Firefox/129.0"],"Sec-Websocket-Key":["FHT83gbTIoOaqaDFTq1QYg=="],"Sec-Fetch-Site":["same-site"],"Pragma":["no-cache"],"Upgrade":["websocket"],"Connection":["keep-alive, Upgrade"],"Accept":["*/*"],"Sec-Websocket-Version":["13"],"Origin":["https://www.doekewartena.nl"],"Sec-Fetch-Mode":["websocket"],"Cache-Control":["no-cache"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"http/1.1","server_name":"doekewartena.nl"}},"bytes_read":0,"user_id":"","duration":0.000144191,"size":0,"status":302,"resp_headers":{"Server":["Caddy"],"Alt-Svc":["h3=\":443\"; ma=2592000"],"Location":["https://www.doekewartena.nl/wss59fps"],"Content-Type":[]}}

3. Caddy version:

v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk=

4. How I installed and ran Caddy:

a. System environment:

Mac Mini running Ubuntu

b. Command:

caddy reload after makking changes to my Caddyfile

d. My complete Caddy config:

See above

This is not required. Websocket proxying works out of the box, that @websockets is only used to separate websocket requests to a different upstream from non-websocket requests.

From your access logs we see:

"host": "doekewartena.nl",

"status": 302,

and

"Location": [ "https://www.doekewartena.nl/wss59fps" ],

Which tells a story: someone connected to doekewartena.nl and was redirected to www.doekewartena.nl, essentially.

This is because you have configured Caddy to redirect all requests:

All you’ll need to do is not configure Caddy to redirect the requests you want to proxy.

1 Like

Thanks I thought the problem would have been way more complex.

I removed the www redirect now and it works!

One thing I’m struggling with tho is that eventually the www is kind of important for the url that you see in the browser.

How can I do the redir but not for the proxy?

Caddy just needs some way to tell which requests should get redirected and which shouldn’t. Thinking about it logically, there’s a few ways to go about it.

Limit the scope of your redirect with a path matcher

You know that requests to /wss59fps want to go to the reverse proxy, so we can invert that logic using the not request matcher.

Then you could use that matcher as a limit for your redir directive. It would look something like:

example.com {
  @myredirect not path /mypath
  redir @myredirect https://www.example.com{uri}

  reverse_proxy /mypath upstream:80
  # In this case, /mypath isn't strictly necessary
  # since only requests for this path will remain
  # after redirecting everything else.
}

Make the reverse proxy act first, and fall back to the redirect

Since this issue occurs because redir is processed first according to directive order, if we can prioritise the reverse_proxy, we can let it handle the requests it wants and “leave the rest” for the redirect.

There’s a few ways to do this: you can put them in a route to override the default directive order, forcing them to execute in order of appearance inside the route block.

example.com {
  route {
    reverse_proxy /mypath upstream:80
    redir https://www.example.com{uri}
  }
}

Alternatively, you could use handle blocks, which let you specify mutually-exclusive sets of directives. handle blocks are executed in order of specificity, so one with a path will go off before one without a path.

example.com {
  handle /mypath {
    reverse_proxy upstream:80
  }

  handle {
    redir https://www.example.com{uri}
  }
}

(Remember that path matchers are literal, so all the above config assumes you wanted to proxy /mypath exactly and not e.g. /mypath/foobar, which would need to use /mypath* as the matcher instead.)

3 Likes

Thank you, this helps a lot.
I will try it a.s.a.p.

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