a month ago
Hey folks and Railway team,
I'm hitting a strange WebSocket issue in one of my Railway services and could use a second pair of eyes. Every WS connection opens successfully, then dies about 72 milliseconds later with code=1006, no error surfaced anywhere. Reproducible with a minimal browser-only test that doesn't touch my app code, so I'm pretty sure it's not a code bug in my project — but I want to check if anyone here has seen this pattern before I lose more hair.
Setup
- Runtime: Node.js custom server wrapping Next.js 16
- WebSocket lib: ws@8.21.0 with
noServer: true+server.on('upgrade', ...) - Auth in handshake: next-auth v5 JWT via
getToken()(pure crypto verify, no DB call, resolves in ~3-15ms consistently) - Deploy: Railway default
.up.railway.appdomain
Symptom
Every WebSocket connection to /ws:
- HTTP upgrade succeeds
- Server-side
wss.emit('connection')runs correctly - ~65ms later the socket is closed with code=1006, empty reason
- The server does NOT initiate the close (no ws.close(), no ws.terminate())
- The
ws.on('error')handler never fires on either side
Isolated reproduction (no app code)
In DevTools Console on any authenticated page of my app:
const ws = new WebSocket('wss://YOUR-APP.up.railway.app/ws');
ws.onopen = () => console.log('OPEN', new Date().toISOString());
ws.onclose = (e) => console.log('CLOSE code=', e.code, 'at', new Date().toISOString());
setTimeout(() => console.log('30s later state:', ws.readyState), 30000);Result on every attempt:
OPENat TCLOSE code= 1006at T + ~2ms (client sees it)readyState: 3at 30s
Railway edge logs (representative entry)
Every /ws request logs like this:
{
"method": "GET",
"path": "/ws",
"httpStatus": 0,
"responseDetails": "connection upgraded to WebSocket",
"totalDuration": 72,
"upstreamRqDuration": 72,
"txBytes": 0,
"rxBytes": 3834,
"upstreamErrors": ""
}Consistently ~72ms total duration, txBytes: 0, upstreamErrors empty. No indication of what caused the close.
Server-side instrumentation (during handshake)
I added logs to the upgrade handler to trace where things go wrong:
upgrade received path=/ws
getToken done in 3ms hasToken=true isValid=true
calling handleUpgrade userId=1
handleUpgrade complete in 4ms — emitting connection
connection handler entered userId=1
CLOSE code=1006 reason="(empty)" lived=63ms userId=1Everything looks healthy on the upstream side. The socket just... dies ~65ms after the connection handler runs.
What I tried (all no-op)
- Application-level heartbeat client-side (never reaches the 25s mark)
- Protocol-level ping/pong from server every 15s with
isAlivetracking - Immediate
ws.ping()on connection open (in case the proxy wants bytes right away) - Immediate
ws.send({type: "HELLO"})on connection open (data frame instead of ping) - TCP tuning on the raw socket before handleUpgrade:
setKeepAlive(true, 15000),setNoDelay(true),setTimeout(0) - Completely stripping Sentry from the code path (no captureException, no captureRequestError,
withSentryConfigdisabled,defaultIntegrations: false, SDK disabled) - Reverting the WebSocket server + client hook + custom server to a byte-for-byte copy of another Railway project of mine that works fine (see comparison below)
Nothing moves the needle. Every attempt closes at the same ~72ms.
Weird part — another Railway project works fine
I have a second Railway service running practically the same architecture (Next.js custom server, ws lib, noServer: true, same JWT-in-handshake pattern). WebSockets in that service stay alive for hours, real-time works flawlessly. When I copy the WS code from the working project byte-for-byte into this broken one, it still fails. So the code path is functionally identical between the two, but only one service exhibits this behavior.
The only variable I can't rule out is something at the Railway service level.
Questions for the community
- Has anyone else seen WebSockets close with code=1006 at a suspiciously consistent ~65-75ms after handshake, on a Railway service?
- Any known scenarios where
txBytes: 0+upstreamErrors: ""in the edge log points to a specific cause (upstream health check, edge idle timeout on WS, region-specific behavior)? - Any way to get more verbose edge/proxy logs than what shows up in the standard log stream?
Happy to share more instrumentation output or reproduce on demand. Thanks in advance!
5 Replies
a month ago
This thread has been opened as a bounty so the community can help solve it.
Status changed to Open Railway • about 1 month ago
a month ago
Try redeploy your service or change the deployment region, is this deployed to US East?
mayori
Try redeploy your service or change the deployment region, is this deployed to US East?
a month ago
Yes, it is US East. I’ll try re-deploying. Thanks and I’ll comment back if this work or not 👍🏼
mayori
Try redeploy your service or change the deployment region, is this deployed to US East?
a month ago
I moved it to West and still not working the sockets. Not sure what it could be. Frustrating
a month ago
Adding the x-railway-request-id: pVKv8gtkTbeqEHVGg4a9AQ ... Still battling with this issue !
a month ago
Status changed to Solved m1gu3l4ngel • 29 days ago