Skip to content

Caddy doesn't forward truncated responses #7845

Description

@garak95

Issue Details

When an upstream responds with a non-zero Content-Length but then closes the connection before sending the content (or sends too small amount of content), Caddy will drop the connection and the client will not receive anything, instead the client connection is abruptly closed.

I'm assuming that Caddy is doing some buffering, realizes the mismatch, and drops everything. However I would expect caddy to be a little more transparent: as long as the response had fully formed headers caddy should pass it along.

To reproduce:
reverse_proxy 10.0.0.2:80 and cause the backend to issue HTTP 500 with Content-Length 123 but then close the connection right after the headers.

Curl sessions

Direct backend response (what I would expect caddy to pass along)

curl http://example.com/demo.mkv -v --resolve example.com:80:10.0.0.8
* Added example.com:80:10.0.0.8 to DNS cache
* Hostname example.com was found in DNS cache
*   Trying 10.0.0.8:80...
* Connected to example.com (10.0.0.8) port 80
* using HTTP/1.x
> GET /demo.mkv HTTP/1.1
> Host: example.com
> User-Agent: curl/8.14.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 500 Internal Server Error
< Server: nginx
< Date: Sun, 28 Jun 2026 16:13:57 GMT
< Content-Type: video/x-matroska
< Content-Length: 135708
< Connection: keep-alive
< Content-Disposition: inline; filename="demo.mkv"
< Accept-Ranges: bytes
<
* end of response with 135708 bytes missing
* closing connection #0
curl: (18) end of response with 135708 bytes missing

What caddy responds instead:

curl https://example.com/demo.mkv -v
* Host example.com:443 was resolved.
* IPv6: (none)
* IPv4: 1.2.3.4
*   Trying 1.2.3.4:443...
* ALPN: curl offers h2,http/1.1
[TLS STUFF...]
* Connected to example.com (1.2.3.4) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://example.com/demo.mkv
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: example.com]
* [HTTP/2] [1] [:path: /demo.mkv]
* [HTTP/2] [1] [user-agent: curl/8.14.1]
* [HTTP/2] [1] [accept: */*]
> GET /demo.mkv HTTP/2
> Host: example.com
> User-Agent: curl/8.14.1
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Request completely sent off
* HTTP/2 stream 1 was not closed cleanly: INTERNAL_ERROR (err 2)
* Connection #0 to host example.com left intact
curl: (92) HTTP/2 stream 1 was not closed cleanly: INTERNAL_ERROR (err 2)

To make sure it wasn't HTTP2 weirdness, I forced caddy to respond in http 1.1 without tls:

curl http://example.com/demo.mkv -v
* Host example.com:80 was resolved.
* IPv6: (none)
* IPv4: 1.2.3.4
*   Trying 1.2.3.4:80...
* Connected to example.com (1.2.3.4) port 80
* using HTTP/1.x
> GET /demo.mkv HTTP/1.1
> Host: example.com
> User-Agent: curl/8.14.1
> Accept: */*
>
* Request completely sent off
* Empty reply from server
* shutting down connection #0
curl: (52) Empty reply from server

The stderr output

{"level":"warn","ts":1782663200.3506744,"logger":"http.handlers.reverse_proxy","msg":"aborting with incomplete response","upstream":"10.0.0.8:80","duration":0.000743837,"request":{"remote_ip":"10.0.0.1","remote_port":"35568","client_ip":"10.0.0.1","proto":"HTTP/1.1","method":"GET","host":"example.com","uri":"/demo.mkv","headers":{"X-Forwarded-Proto":["http"],"X-Forwarded-Host":["example.com"],"Via":["1.1 Caddy"],"Accept":["*/*"],"User-Agent":["curl/8.14.1"],"X-Forwarded-For":["10.0.0.1"]}},"error":"reading: unexpected EOF"}

Assistance Disclosure

AI not used

If AI was used, describe the extent to which it was used.

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions