[HELP] CleanSlate deployment errors with ZORAXY and websocket connections
What happened? I am triying to proxy a new service that has a websocket. It is called cleanslate. Ive managed to get it working but the websocket connection, which should be handled by ZORAXY does not work. I get this error
[websocket] [system:error] Couldn't dial to remote backend url ws://graphql-server:8080/graphql: websocket: bad handshake
Describe what have you tried Ive configured my ZORAXY like this
ive used a virtual directory to pass the AUTH, which works fine, and the graphql server for the websocket.
The error I am seeing is ZORAXY passing the connection to the graphql container. If I delete that rule from the virtual directory it tries connecting to the client container and gives the same error
[websocket] [system:error] Couldn't dial to remote backend url ws://client:3000/v1/graphql: websocket: bad handshake
Ive tried adding to the websocket rule the /v1 and /v1/graphql terminations, but I get the same error.
From the graphql logs we can see this
ERR detail={"connection_info":{"msg":null,"token_expiry":null,"websocket_id":"52e5bac0-9330-4b1a-a020-6663ad760fcf"},"event":{"detail":{"code":"not-found","error":"only '/v1/graphql', '/v1alpha1/graphql' and '/v1beta1/relay' are supported on websockets","path":"$"},"type":"rejected"},"user_vars":null} timestamp=2025-04-04T05:21:03.666+0000 type=websocket-log
Describe the networking setup you are using I have Zoraxy deployed with DOCKER. I have a PROXIED docker network where I put every service I need to proxy, in this case the 3 containers I mentioned are within that network.
Additional context Ive deployed this new application via docker, it consist of 3 containers.
- The client
- The graphql-server
- Authentication-server
The nginx config that the creator recommends is this
http {
server {
listen 443 http2 ssl;
listen [::]:443 http2 ssl;
server_name XXX;
ssl_certificate XXX
ssl_certificate_key XXX;
# HTTP Security Headers
add_header Referrer-Policy "strict-origin";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains;";
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "DENY";
add_header X-XSS-Protection "0";
# You can remove the Google, Firebase, and Sentry policies if you are not using them
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' https://apis.google.com https://www.google.com https://www.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; connect-src 'self' https://*.ingest.sentry.io https://identitytoolkit.googleapis.com https://securetoken.googleapis.com https://apis.google.com https://world.openfoodfacts.org; frame-src 'self' https://*.firebaseapp.com https://www.google.com; img-src 'self' https://www.gstatic.com data:; font-src 'self' https://fonts.gstatic.com https://fonts.googleapis.com; worker-src 'self'; object-src 'none';"
add_header Permissions-Policy "accelerometer=(self), autoplay=(self), camera=(self), cross-origin-isolated=(self), display-capture=(self), encrypted-media=(self), fullscreen=(self), geolocation=(self), gyroscope=(self), keyboard-map=(self), magnetometer=(self), microphone=(self), midi=(self), payment=(self), picture-in-picture=(self), publickey-credentials-get=(self), screen-wake-lock=(self), sync-xhr=(self), usb=(self), xr-spatial-tracking=(self)"
location /v1 {
# API (Hasura)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'Upgrade';
proxy_set_header Host $host;
proxy_pass http://localhost:8080;
}
location /v2 {
# API (Hasura)
proxy_pass http://localhost:8080;
}
location /console {
# Admin panel (Hasura)
proxy_pass http://localhost:8080;
add_header Content-Security-Policy "";
}
location /auth {
# Authentication server (Express.js)
proxy_pass http://localhost:3001;
}
location /healthz {
# Health check (Hasura)
proxy_pass http://localhost:8080;
}
location / {
# Static files (Clean Slate)
proxy_pass http://localhost:3000;
}
}
}
As it can be seen in there, there is a websocket, and a auth proxy_pass for the auth server
I really do not know what is happening here, but I am sure I am doing something wrong on ZORAXY.
Any help would be amazing!
Hi @Mega61 ,
I assume you are referring to this project.
The short answer is I have no idea.
The long answer is that they are not a self-contained system. They are using Caddy or Nginx as a web server middleware that bind services together (as you can see from their exceptionally long reverse proxy config). It is a common way to bind services together as a web development beginner and it is a quick way to get a prototype done, but the down side is making the project dependent on an external service, like nginx / Caddy in this case.
Zoraxy is a reverse proxy and it is not a fully featured web server like nginx. Sometime it might be compatible with systems like this but this really down to the implementation of the upstream system. Assuming your hostname are correct for upstreams (graphql-server:8080 makes no sense to me, but I will assume you replace this with some dummy text to prevent showing real public ip on the internet), your config should works.
For your information, Websocket works in both host name based routing and virtual directory routing, and your graphQL side actually see something connecting in, so Zoraxy did proxied the request to the correct location, just the payload that it proxied is not what the graphQL server wanted and the graphQL side close the connection (and hence the bad handshake see in Zoraxy log).
I would recommend you to ask the developer of the cleanslate project to add support for Zoraxy by improving their system design to depend less on external reverse proxy for maintaining their API structure. Alternatively, you can use an nginx instance as middleware for such system to workaround the problem.