Troubleshooting

Why do large uploads through Traefik fail after exactly 60 seconds?

The entryPoint readTimeout (60s since v2.11.2) bounds the whole request body. Which entryPoint needs it, what HTTP/1.1, HTTP/2 and HTTP/3 clients get, tested.

· 15 min read · How we verify this

Key points

  • The 60 seconds is the entryPoint's transport.respondingTimeouts.readTimeout, the "maximum duration for reading the entire request, including the body". It has defaulted to 60s since v2.11.2 and v3.0.0-rc5 (11 April 2024). An upload still sending at 60.0s is cut off however steadily it is moving.
  • The setting is per entryPoint. Raising it on web does nothing for HTTPS uploads, which arrive on websecure: with web set to 0, a slow upload to websecure still failed at 60 seconds on Traefik 3.7.15.
  • What the client gets depends on the protocol. HTTP/1.1 got 499 Client Closed Request or 504 Gateway Timeout (a race: 6 and 9 times in 15 runs), HTTP/2 got 502 Bad Gateway every time, HTTP/3 got 504. None of it reaches Traefik's log at the default level.
  • HTTP/3 was exempt until v3.7.12: on 3.7.11 an HTTP/3 upload ran for 82 seconds and succeeded (CVE-2026-88012). From 3.7.12 one readTimeout covers HTTP/1.1, HTTP/2 and HTTP/3 on the entryPoint.
  • The timer also runs while a slow backend holds the body back: a fast client to a backend reading at 50 KiB/s got a 504 at 63.9 seconds. Once the body is in, it stops: a backend that took 90 seconds to answer still returned 200.

An upload through Traefik that dies at exactly 60 seconds has hit the entryPoint's readTimeout. Traefik's reference defines it as "the maximum duration for reading the entire request, including the body", and its default is 60s. Raise it on the entryPoint the upload actually arrives on, which for HTTPS is usually websecure, not web:

bash
--entryPoints.websecure.transport.respondingTimeouts.readTimeout=600s

The confusion comes from what the client is told. The status is a 504, a 502 or even a 499, depending on the protocol, and the 504 points everyone at the backend. The backend is not the problem: it was receiving the body normally until Traefik stopped reading it. Since Traefik v2.11.2 this timer is on by default, and before v3.7.12 it did not apply to HTTP/3, which is why the same upload can fail for one client and succeed for another.

What you see at 60sProtocolWhat it means
499 Client Closed Request, 21-byte bodyHTTP/1.1readTimeout fired; Traefik sent the 499 to a client that was still connected
504 Gateway Timeout, 15-byte bodyHTTP/1.1 or HTTP/3readTimeout fired, same cause; HTTP/1.1 gives 499 or 504 at random
502 Bad Gateway, 11-byte bodyHTTP/2readTimeout fired, same cause
504 after exactly your responseHeaderTimeoutanyThe backend was slow to answer after it had the whole body
524 from Cloudflare after about 125sanyCloudflare gave up waiting for the origin's response
413 Request Entity Too LargeanyA size limit, not a timer: see the Buffering row below

How the cases were reproduced#

Everything below was observed on 31 October 2026 unless it is attributed to documentation. Traefik 3.7.15 ran from the official image with the file provider, entryPoints web (:80) and websecure (:443, HTTP/3 enabled, default certificate), and one router per entryPoint to a Python backend that counted the bytes it read. Traefik 3.7.11 was run for the HTTP/3 comparison. HTTP/1.1 and HTTP/2 clients were curl 8.23.0 with --limit-rate 100k, which uploads at 102,400 bytes a second, so an 8,000,000-byte body needs about 78 seconds. The HTTP/3 client was aioquic 1.7.0 sending at the same rate. All hops ran on one Docker network, so there was no real network latency. Kubernetes and Cloudflare were not run; their rows come from the chart source and Cloudflare's documentation.

The timer: one deadline for the whole request#

The entryPoint reference lists three responding timeouts. Their defaults, in Traefik v3:

SettingDefaultCovers
respondingTimeouts.readTimeout60s"reading the entire request, including the body"
respondingTimeouts.writeTimeout0s"from the end of the request header read to the end of the response write"
respondingTimeouts.idleTimeout180san idle keep-alive connection

readTimeout is a deadline, not an idle timer. A client sending a steady 100 KiB a second, with no pause at all, was cut off at 60.0 seconds after 6,144,000 of its 8,000,000 bytes, which is 60 seconds of data at that rate. Nothing about making progress extends it. So the question is never "is the upload stalling" but "can the whole body arrive in under 60 seconds". For a body of size S from a client that uploads at rate R, that takes S / R: 100 MB at 10 Mbit/s is 80 seconds, so it fails, and the same upload at 20 Mbit/s takes 40 seconds and passes. That is why these reports so often come from one office, one mobile network or one large file.

The 60-second default dates from April 2024. Advisory GHSA-4vwx-54mw-vqfw (CVE-2024-28869) reported that a request with a Content-Length header and no body caused "an indefinite hang with the default configuration", and gave configuring "the readTimeout option" as the workaround. The patched releases were v2.11.2 and v3.0.0-rc5, both dated 11 April 2024. The advisory does not mention a default change; the changelog does. Its v2.11.2 entry lists "Set default ReadTimeout value to 60s" (#10602) and "Revert LingeringTimeout and change default value for ReadTimeout" (#10599), and v3.0.0-rc5 merged that branch. An installation upgraded across that release, with no readTimeout in its configuration, went from no bound to a 60-second bound on every upload without anyone touching it.

The reference adds that setting these "has no effect for UDP entryPoints". That means UDP routing entryPoints. HTTP/3 is served from a TCP entryPoint with http3 enabled, and is covered by the same readTimeout from v3.7.12, as shown below.

What the client sees at 60 seconds, by protocol#

Fifteen HTTP/1.1 uploads (plain and TLS, with and without Expect: 100-continue), five HTTP/2 uploads and one HTTP/3 upload hit the default timer on 3.7.15:

ProtocolStatus the client gotTimesTraefik debug log line
HTTP/1.1499 Client Closed Request6 of 15499 Client Closed Request error="context canceled"
HTTP/1.1504 Gateway Timeout9 of 15504 Gateway Timeout error="readfrom tcp ...->backend:8000: read tcp ...:80->client: i/o timeout"
HTTP/2502 Bad Gateway5 of 5502 Bad Gateway error="readfrom tcp ...->backend:8000: i/o timeout"
HTTP/3504 Gateway Timeout1 of 1504 Gateway Timeout error="readfrom tcp ...->backend:8000: deadline exceeded"

Each run ended between 60.0 and 61.0 seconds. Three details matter when you read these.

The 499 is real, and it went over the wire. nginx's 499 is a log code that is never sent, as what nginx 499 means explains. Traefik sent 499 with the body Client Closed Request to a client that had not closed anything: curl was still uploading and received it with exit code 0. Which of 499 and 504 an HTTP/1.1 client gets is a race between two ways the same deadline surfaces inside Traefik, and it changed from run to run with identical requests. Treat them as one event.

The status names the wrong hop. The 504 and 502 here mean a gateway problem in the usual reading of 502 vs 503 vs 504, but the backend was healthy throughout. It saw the request start at once, read the body as it streamed in, and then got an end of file partway through: 6,144,000 bytes in every HTTP/1.1 and HTTP/2 run. The readfrom tcp ... read tcp ...:80->client wording in the debug line gives it away: the failed read was the one from the client, inside the copy towards the backend.

At the default log level, Traefik says nothing. All four lines above are DBG. With no --log.level set and no access log, a timed-out upload left no trace in Traefik's output at all. With --accesslog=true each one is logged with its status and a duration of 60000ms, which is the line to search for:

text
10.201.13.7 - - [31/Oct/2026:08:41:24 +0000] "POST /up HTTP/1.1" 504 15 "-" "-" 4 "secure@file" "http://backend:8000" 60000ms

A run of 499, 502 or 504 entries all at 60000 to 61000 ms, all POST or PUT, is this timer.

The setting is per entryPoint#

readTimeout belongs to an entryPoint, so it applies to every router on that entryPoint and to nothing else. This is the usual reason a fix appears not to work. With --entryPoints.web.transport.respondingTimeouts.readTimeout=0 and websecure left alone, the same two slow uploads went:

UploadArrived onResult
http://web (readTimeout 0)200 after 78.0s, all 8,000,000 bytes
https://websecure (default 60s)504 after 61.0s, 6,246,400 bytes

If web only redirects to HTTPS, a timeout raised there never meets an upload. Then the reverse, websecure set to 600s: HTTP/1.1, HTTP/2 and HTTP/3 uploads to it all returned 200 after 78 to 82 seconds, while a plain HTTP upload to web in the same run still got a 504 at 60 seconds.

How to set it, for the entryPoint called websecure:

yaml
# static configuration (traefik.yml)
entryPoints:
  websecure:
    address: ":443"
    transport:
      respondingTimeouts:
        readTimeout: 600s
bash
# CLI, Docker command or Kubernetes container args
--entryPoints.websecure.transport.respondingTimeouts.readTimeout=600s
yaml
# Helm chart values (chart 42.2.0, appVersion v3.7.16)
ports:
  websecure:
    transport:
      respondingTimeouts:
        readTimeout: 600s

The Helm chart renders that value as --entryPoints.websecure.transport.respondingTimeouts.readTimeout=600s in the container arguments. ports.web has the same transport block, and the key is the entryPoint name, not the port: the chart's websecure listens on 8443 inside the pod. Like everything under entryPoints, this is static configuration. It cannot go in a dynamic file, a Docker label or an IngressRoute, and it takes effect on restart. Where routers, entryPoints and the static/dynamic split sit is covered in Traefik routers, services and middlewares.

HTTP/3 was exempt until v3.7.12#

CVE-2026-88012, published in the GitLab advisory database on 10 September 2026, is titled "respondingTimeouts.readTimeout is not applied to HTTP/3, leaving slow-body uploads unbounded". Its explanation is that the timeout "is enforced as a deadline on the TCP connection, which cannot reach a QUIC stream". It lists all versions from 3.0.0 before 3.7.12 as affected. The changelog entry is "Apply read timeout, idle timeout, and max header bytes for HTTP/3" (#13717), in v3.7.12 (26 August 2026) and in v2.11.56 on the v2 branch.

The test agrees. On 3.7.11, an HTTP/3 upload of 8,192,000 bytes at the same 100 KiB a second ran for 82.0 seconds and got a 200, while an HTTP/1.1 upload started at the same moment got a 504 at 60 seconds. On 3.7.15, the HTTP/3 upload got a 504 at 60.1 seconds.

This explains the reports where uploads work in one browser and fail in another, or fail after an upgrade. A browser that has picked up Traefik's Alt-Svc advertisement may upload over HTTP/3, and before 3.7.12 those uploads ignored the timer. Upgrading to 3.7.12 or later applied the 60 seconds to them for the first time. If your HTTP/3 uploads started failing at 60 seconds after an upgrade, that is the fix working, and the remedy is the same readTimeout as for the other protocols.

A slow backend uses up the client's 60 seconds#

Traefik streams the request body to the backend as it reads it; it does not buffer by default, as the comparison in proxy buffering and streaming shows. That has a consequence the documentation's wording does not make obvious. When the backend reads slowly, TCP flow control slows Traefik's reads from the client, and the time spent waiting counts against readTimeout.

A client uploading 8 MB with no rate limit, to a backend that read only 50 KiB a second, got a 504 Gateway Timeout after 63.9 seconds. The backend had read 3,784,704 bytes. The client was fast; the deadline was still on the client side, because that is the read that was waiting. So a 60-second failure on a fast network does not rule out readTimeout: an application that writes uploads straight to slow storage, or scans them while reading, produces the same symptom.

Once the body has been read in full, the timer has done its job. A backend that read 8 MB at full speed and then slept for 90 seconds before answering returned 200 after 90 seconds, over HTTP/1.1 and HTTP/2, with every timeout at its default. Traefik will wait indefinitely for the response by default, because the next timer, responseHeaderTimeout, is 0.

Buffering middleware: same timer, different log#

The Buffering middleware reads the whole body before it forwards anything, so with it attached a slow upload never reaches the backend. On 3.7.15 a slow upload through it still failed at 60 seconds with 499 Client Closed Request, the access log showed the service as -, and the backend saw nothing. This is the one case that logs at the default level:

text
ERR vulcand/oxy/buffer: error when reading request body, err: context canceled entryPointName=web middlewareName=buf@file middlewareType=Buffer routerName=plainbuf@file

Buffering adds size limits rather than timers. maxRequestBodyBytes defaults to 0, unlimited; over a non-zero limit the client gets a 413 and the request "is not forwarded to the Service". Bodies over memRequestBodyBytes (1,048,576 by default) are buffered on disk instead of in memory. How the same limit is enforced, and reported, by nginx, Caddy, HAProxy and Envoy is set out in why nginx returns 413.

The ladder: every timer a large upload crosses#

An upload from a browser through a CDN and Traefik to an application crosses each of these. The defaults are the documented ones; the Traefik rows were also tested on 3.7.15 as described above.

TimerHopDefaultStartsFires as
readTimeoutTraefik entryPoint60s (v2.11.2+, v3.0.0-rc5+; HTTP/3 from v3.7.12)Request starts arriving499 or 504 (HTTP/1.1), 502 (HTTP/2), 504 (HTTP/3)
writeTimeoutTraefik entryPoint0s (off)End of request headersResponse write cut off
idleTimeoutTraefik entryPoint180sBetween requests on a keep-alive connectionIdle connection closed
dialTimeoutserversTransport30sConnecting to the backendConnection attempt abandoned
responseHeaderTimeoutserversTransport0s (off)After the request, "including its body", is written504, debug net/http: timeout awaiting response headers
idleConnTimeoutserversTransport90sPooled backend connection idlePooled connection closed
maxRequestBodyBytesBuffering middleware0 (unlimited)Size, not time413
Proxy Write TimeoutCloudflare to origin30s, not configurableWriting to the origin524
Proxy Read TimeoutCloudflare to origin125s, Enterprise up to 6,000sWaiting for the origin's response524
Application server timeoutsBackendFramework dependentUsually reading the bodyVaries

responseHeaderTimeout was tested to confirm where it starts. Set to 30s, it returned a 504 at 30.0 seconds to a backend that slept 45 seconds after the body, but let through a 3 MB upload that took 58 seconds to send, with a 200. It is the timer for a slow application, and it does not limit uploads. Its 0 default is also why the ladder has a hole: a backend that never answers is waited on indefinitely.

The Cloudflare figure is 125 seconds, not 100. Its connection limits page gives 125 seconds for the Proxy Read Timeout and says it is the only one an Enterprise customer can change; the 524 page gives the same default and a maximum of 6,000 seconds. Both timers are between Cloudflare and the origin, and the documentation describes the 524 as the origin not providing "an HTTP response" in time. Cloudflare was not tested here, so how its 30-second write timeout behaves during a long upload is not stated.

The ladder rule from timeout budgets across a proxy chain applies to the upload direction too: each inner hop needs to allow at least as long as the hops outside it, or the innermost short timer decides. The timeout ladder checker does that comparison for a chain you enter.

Failure modes#

Uploads over HTTPS fail at 60s after raising readTimeout. It was raised on web and the uploads arrive on websecure. Set it on the entryPoint in the access log's router name, or on both.

Some users fail at 60s, others never do. It is upload speed, not file size alone: the body must arrive within 60 seconds. On versions before 3.7.12, HTTP/3 clients were also exempt.

HTTP/3 uploads started failing after an upgrade. v3.7.12 applied readTimeout to HTTP/3 (CVE-2026-88012). Raise it on the entryPoint like the others.

A 504 at 60s with a fast client. The backend reads the body slowly and Traefik's read from the client waits on it. Speed up the backend's reading, or raise readTimeout.

A 504 at some other fixed time after the upload completes. responseHeaderTimeout on the serversTransport. It starts after the body is written, so it limits processing time, not upload time.

No error in Traefik's log. The timeout lines are debug level. Turn on the access log and search for durations of about 60000ms.

The setting has no effect at all. It was put in a dynamic file or a label. EntryPoint transport settings are static configuration and need a restart.

Where this answer stops applying#

The behaviour was observed on Traefik 3.7.15, with the HTTP/3 comparison on 3.7.11, using curl 8.23.0 and aioquic 1.7.0 on a single Docker network. Browsers were not tested, so what a browser shows for the 499 or 502 is not stated. The 499/504 split for HTTP/1.1 is a race and the counts above are from 15 runs, not a rate to rely on. Traefik v2 releases from v2.11.2 have the same 60-second default according to the changelog, but were not run. The Kubernetes rows come from the Helm chart source at chart version 42.2.0, not from a cluster, and distributions that install Traefik for you may pass their own values. Cloudflare and application server timeouts are stated from documentation only. To find which hop gave up when the chain is longer than this, the proxy debugging playbook bisects it.

Frequently asked questions#

What is the default readTimeout in Traefik?#

It is 60 seconds, set on each entryPoint as transport.respondingTimeouts.readTimeout. It became the default in v2.11.2 and v3.0.0-rc5, in April 2024, and covers reading the entire request including the body.

Why does my Traefik upload fail after exactly 60 seconds?#

The whole request body has to arrive within the entryPoint's readTimeout, 60 seconds by default, even if the upload is moving steadily. Raise it on the entryPoint the upload arrives on, usually websecure for HTTPS.

How do I set readTimeout in the Traefik Helm chart?#

Set ports.websecure.transport.respondingTimeouts.readTimeout, for example to 600s, in the chart values. The chart turns it into the container argument --entryPoints.websecure.transport.respondingTimeouts.readTimeout=600s.

Why does Traefik return 499 or 502 instead of 504 for a timed-out upload?#

The status depends on the protocol. HTTP/1.1 clients got 499 or 504 at random for the same timeout, HTTP/2 clients got 502, and HTTP/3 clients got 504. All of them mean the entryPoint's readTimeout fired while Traefik was still reading the body.

Does readTimeout apply to HTTP/3 in Traefik?#

From v3.7.12, yes. Before that, HTTP/3 requests were not bound by it (CVE-2026-88012), so an HTTP/3 upload could take longer than 60 seconds while the same upload over HTTP/1.1 or HTTP/2 failed.

Is responseHeaderTimeout the setting for slow uploads?#

No. responseHeaderTimeout on the serversTransport starts only after the request, including its body, has been written to the backend, and its default is 0 (no limit). It limits how long the application takes to answer, not how long the upload takes.

Should I set readTimeout to 0?#

Not on a public entryPoint. The 60-second default is the fix for CVE-2024-28869, a request that hung indefinitely. Set it to the longest legitimate upload time, or move large uploads to an entryPoint of their own.

Primary sources#

Every normative claim on this page is checked against the specification or the vendor documentation listed here. Where behaviour is version dependent, the version is named in the text.

  1. Traefik documentation, EntryPoints reference (transport.respondingTimeouts)
  2. Traefik documentation, ServersTransport reference (forwardingTimeouts)
  3. Traefik documentation, Buffering middleware
  4. Traefik security advisory GHSA-4vwx-54mw-vqfw (CVE-2024-28869)
  5. GitLab Advisory Database, CVE-2026-88012 (readTimeout not applied to HTTP/3)
  6. Traefik CHANGELOG (v2.11.2, v3.0.0-rc5, v3.7.12)
  7. Traefik Helm chart values.yaml and pod template (chart 42.2.0)
  8. Cloudflare, Connection limits
  9. Cloudflare, Error 524

Found something wrong, or behaviour that differs on your version? Report it with the version number and a primary source. Anything substantive is fixed in the page and logged on the corrections page. See editorial standards for how pages are researched, sourced and reviewed.

More in troubleshooting proxies#