17 days ago
Service: layermate-website | Volume ID: vol_qlmd2hu8mj44y8bf | Plan: Free, then upgraded to Hobby — identical failure on both
Symptom
Uploading a ~172.5MB file to an authenticated admin endpoint fails almost every time. Rebuilt to upload in 8MB chunks as separate sequential POST requests (~22 total) to work around it, but the interruption still happens on nearly every attempt — just mid-chunk now instead of mid-file.
Fails consistently after ~19-22 requests / ~80-90 seconds in, regardless of file or chunk size.
Every failure shows the same trace server-side:
Error: Request aborted
at IncomingMessage.<anonymous> (/app/node_modules/multer/lib/make-middleware.js:124:28)
at abortIncoming (node:_http_server:838:9)
at socketOnClose (node:_http_server:832:3)
at Socket.emit (node:events:531:35)
at TCP.<anonymous> (node:net:347:12)The underlying TCP socket for an in-flight request is closed from outside the Node process.
Sometimes immediately followed by the container itself stopping:
npm error signal SIGTERM
npm error command sh -c node server.js
Stopping Container
One run's npm debug log timestamp: 2026-08-04T08_53_30_338Z.
Client-side, it's a generic Failed to fetch — consistent with a reset connection, not an HTTP error response.
Ruled out, with evidence
Disk/volume space — Volume Usage showed ~200-300MB used of 500MB at failure time.
CDN/WAF in front — DNS/headers resolve straight to Railway's edge (server: railway-hikari, x-railway-edge), no Cloudflare etc.
App body-size limits — no limit applies to the multipart routes; multer's fileSize cap is above the 8MB chunk size.
Node/Railway timeouts — requestTimeout/headersTimeout raised to 14 min app-side; failures happen at ~80-90s, nowhere close.
RAM — identical failure on Free (512MB) and Hobby (48GB); not plausible an 8MB-chunk upload exhausts 48GB.
Healthcheck — Healthcheck Path is empty for this service.
Mid-upload redeploy — Deployments tab showed no new deployment at failure time.
Our own rate limiting — only applied to /api/login, /api/signup, /api/forgot-password, never these routes.
Memory leak — 15 consecutive full 150MB upload cycles (2.25GB total) run locally against the same code; RSS stayed flat 234-254MB, no growth.
Client network — same file uploaded to Google Drive on the same connection, no issues, ~2 minutes (longer than our failure window).
Simple connection-flood block — 30 rapid GETs from an unrelated network all returned 200 in ~0.35s each, no degradation (though this doesn't fully replicate authenticated POSTs with an 8MB payload, which I couldn't test without a live session).
What we're asking
This looks like something between Railway's edge and this container that isn't visible app-side. Could you check:
Any recorded restart/stop events for this service around the failure timestamps, and their stated cause?
Any edge/proxy-level connection limits or anti-abuse heuristics that could reset a burst of ~20 sequential authenticated POSTs over ~80-90 seconds?
This service's actual configured resource limits, in case a lower per-service cap is still in effect separate from the account plan ceiling?
Happy to provide exact timestamps, project/service ID, or anything else useful.
Pinned Solution
17 days ago
If you're switching to Cloudflare R2 presigned URLs,
- S3 Client Setup: Configure @aws-sdk/client-s3 using your Cloudflare Account ID and R2 credentials.
- Presigned URL Generator: Generate a 15-minute PutObjectCommand presigned URL via @aws-sdk/s3-request-presigner on your Node backend.
- Direct Client Upload: Fetch the presigned URL from the browser and PUT the file payload directly to R2.
- CORS Configuration: Add ["GET", "PUT", "HEAD"] to allowed methods in your Cloudflare R2 bucket CORS settings.
6 Replies
17 days ago
This thread has been opened as a bounty so the community can help solve it.
Status changed to Open Railway • 17 days ago
17 days ago
You have to generate presigned S3/R2 URLs on the Node server and upload chunks directly from the browser to object storage.
Try to also Pass the node flags --max-old-space-size=4096 and explicitly force garbage collection hints
17 days ago
On the memory flags: I don't think that's it in this case — we stress-tested the exact upload code locally with 15 consecutive 150MB cycles (2.25GB total) and RSS stayed flat in a 234–254MB band with no growth trend, and the identical failure also occurred on our Hobby plan (48GB RAM), so a heap ceiling doesn't fit the evidence.
17 days ago
I’d recommend using object storage for large file handling, instead of the files going directly into the volume. You’ll be able to generate presigned URLs for direct or multipart uploads.
hellrazor777
On the memory flags: I don't think that's it in this case — we stress-tested the exact upload code locally with 15 consecutive 150MB cycles (2.25GB total) and RSS stayed flat in a 234–254MB band with no growth trend, and the identical failure also occurred on our Hobby plan (48GB RAM), so a heap ceiling doesn't fit the evidence.
17 days ago
Try adding a 50–100ms artificial delay (await new Promise(r => setTimeout(r, 100))) between sequential POST chunk requests to allow Envoy's buffer and Node’s I/O queue to flush back to zero.
h707
Try adding a 50–100ms artificial delay (await new Promise(r => setTimeout(r, 100))) between sequential POST chunk requests to allow Envoy's buffer and Node’s I/O queue to flush back to zero.
17 days ago
No Luck we will go down the R2 setup
hellrazor777
No Luck we will go down the R2 setup
17 days ago
If you're switching to Cloudflare R2 presigned URLs,
- S3 Client Setup: Configure @aws-sdk/client-s3 using your Cloudflare Account ID and R2 credentials.
- Presigned URL Generator: Generate a 15-minute PutObjectCommand presigned URL via @aws-sdk/s3-request-presigner on your Node backend.
- Direct Client Upload: Fetch the presigned URL from the browser and PUT the file payload directly to R2.
- CORS Configuration: Add ["GET", "PUT", "HEAD"] to allowed methods in your Cloudflare R2 bucket CORS settings.
Status changed to Solved medim • 17 days ago
