Should Caddy's JSON access log show the client's `Transfer-Encoding: chunked` header, or is it consumed before logging?

1. The problem I’m having:

I’m trying to verify if Caddy’s access logs (specifically the request.headers field when using format json) will show the Transfer-Encoding: chunked header if it’s sent by the client.

Currently, when I send a request with Transfer-Encoding: chunked to Caddy, this header does not appear in Caddy’s JSON access log under request.headers. However, my upstream application (Gunicorn/Flask) does see an HTTP_TRANSFER_ENCODING: chunked header, which I understand maybe that Caddy itself is sending to the upstream???

My specific question is: Should Caddy’s standard JSON access log (at INFO level) include the Transfer-Encoding: chunked header in request.headers if it was part of the original client request to Caddy, or is this header typically processed and consumed by Caddy before the access logging stage due to its hop-by-hop nature? I want to understand if this is expected behavior or if I need to configure Caddy differently to see this specific incoming header in the logs.
The same happens even at DEBUG logging, that is, caddy still doesn’t see the Transfer-Encoding: chunked header

Here’s an example curl command I’m using to send a chunked request:

echo "This is some chunked data." > data.txt
echo "Another line of data." >> data.txt

Then, the curl command:

curl -vL -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "Transfer-Encoding: chunked" \
  --data-binary @data.txt \
  http://localhost/

2. Error messages and/or full log output:

The JSON log output for a POST request (note the absence of Transfer-Encoding in request.headers even if the client sent it):

{
  "level": "info",
  "ts": 1748458248.0549488,
  "logger": "http.log.access.log0",
  "msg": "handled request",
  "request": {
    "remote_ip": "::1",
    "remote_port": "54566",
    "proto": "HTTP/1.1",
    "method": "POST",
    "host": "localhost",
    "uri": "/",
    "headers": {
      "Accept": [
        "*/*"
      ],
      "Content-Type": [
        "application/x-www-form-urlencoded"
      ],
      "User-Agent": [
        "curl/8.13.0"
      ]
    }
  },
  "user_id": "",
  "duration": 0.009299079,
  "size": 4611,
  "status": 200,
  "resp_headers": {
    "Server": [
      "Caddy"
    ],
    "Content-Type": [
      "text/html; charset=utf-8"
    ],
    "Content-Length": [
      "4611"
    ],
    "Date": [
      "Wed, 28 May 2025 18:50:48 GMT"
    ]
  }
}

Upstream Gunicorn/Flask log (showing it received Transfer-Encoding from Caddy):

{
 'CONTENT_TYPE': 'application/x-www-form-urlencoded',
 'HTTP_TRANSFER_ENCODING': 'chunked',
 'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36',
 'HTTP_X_FORWARDED_FOR': '127.0.0.1',
 ...
}

3. Caddy version:

2.6.2

4. How I installed and ran Caddy:

sudo apt install caddy

a. System environment:

Linux parrot 6.12.12-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.12-1parrot1 (2025-02-27) x86_64 GNU/Linux

b. Command:

caddy run

c. Service/unit/compose file:

N/A

d. My complete Caddy config:

:80 {
	reverse_proxy localhost:8000 {
		header_down -Server
	}
	log {
        output stdout
        format json
        level INFO
    }
}

5. Links to relevant resources:

The header is parsed by the Go standard library as it parses the HTTP request, then deletes it from the set of headers after flagging the respective data field in a data type

The header you see in your upstream app is originating from Caddy, not the downstream client, because the data transfer requires it.

This is also true, somewhat. The Transfer-Encoding header is parsed and removed by the Go standard library, not Caddy, but Caddy also removes the defined hop-by-hop headers.

In short, it’s expected behavior.

In Caddy v2.9.0 there was this enhancement:

2 Likes

Oh, good catch! With that, don’t use the package by Debian. They aren’t talking to us, and their package is ages behind. Use our package.

2 Likes

Using the latest version of caddy solved the problem(not sure if I would call it a problem though, more of like intended behaviour)

I now see the Transfer-Encoding header in my logs as the below:-

"request": {
    "remote_ip": "127.0.0.1",
    "remote_port": "42802",
    "client_ip": "127.0.0.1",
    "proto": "HTTP/1.1",
    "method": "POST",
    "host": "localhost:2018",
    "uri": "/",
    "headers": {
      "Origin": [
        "http://localhost:2018"
      ],
      "Content-Type": [
        "application/x-www-form-urlencoded"
      ],
      "User-Agent": [
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
      ],
      "Referer": [
        "http://localhost:2018/"
      ]
    },
    "transfer_encoding": [    // now present
      "chunked"
    ]
  }

Thanks.

2 Likes

Debians policy is to freeze packages at the moment of release of debians version to guarantee their long term support

The current version is bookworm, released at 10-06-2023. At this date, Caddy 2.6.4 was the latest, they currently have Caddy 2.6.2 in their repos, with 5 different sets of patches applied on top

Don’t use Debian packages if you need cutting edge features

1 Like