Authorization header not passed to downstream reverse proxy

1. The problem I’m having:

I am using the reverse proxy with a matcher to redirect requests to the proper backend.

The routing works as expected and all requests are received by the backend. Everything is handled with transparency except that the Authorization header is not passed downstream.
All other parts of the HTTP request, headers, body etc are correctly passed down.

2. Error messages and/or full log output:

I am logging all the headers received by my backend:

2025-06-25T11:53:15.779555794Z 2025-06-25T11:53:15Z trace Request headers | Content-Type=application/json X-Client-Id=<redacted> X-Forwarded-Host=<redacted>.it User-Agent=IntelliJ HTTP Client/GoLand 2025.1.2 Content-Length=875384 Accept=*/* X-Forwarded-For=<redacted> X-Forwarded-Proto=https X-Service-Id=<redacted> Accept-Encoding=br, deflate, gzip, x-gzip 

3. Caddy version:

lucaslorentz/caddy-docker-proxy:2.8.10
which in turn uses the same Caddy version

4. How I installed and ran Caddy:

Docker setup, see above.

d. My complete Caddy config:

dev.api.sly.it {
	@Matcher {
		header X-Client-ID <redacted>
	}
	handle @Matcher {
		reverse_proxy 10.20.14.11:1323 -- internal container address resolved automatically by caddy-docker-proxy
	}
}

Can you please clarify? Usually,

  • upstream = towards the back-end servers
  • downstream = towards the clients

Is that what you mean?

Sorry for the misuse of the terms.

I meant upstreams towards the back-end servers.

The client correctly sends the request with the Authorization header.

The server (behind the reverse proxy) receives the request without the Authorization header.

I can’t replicate your problem. Not sure whether you’re talking about the Basic or Bearer authentication, so here’s the test case for both:

Caddyfile

:80 {
	@Matcher1 {
		header X-Client-ID 1
	}
	handle @Matcher1 {
		reverse_proxy 127.0.0.1:8081
	}
	@Matcher2 {
		header X-Client-ID 2
	}
	handle @Matcher2 {
		reverse_proxy 127.0.0.1:8082
	}
}

## Authentication Basic
:8081 {
	basic_auth {
		## Username "Bob", password "hiccup"
		Bob $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG
	}
	respond "Welcome, {http.auth.user.id}" 200
}

## Authentication Bearer
:8082 {
	@bearer {
		header Authentication "Bearer 123"
	}
	handle @bearer {
		respond "Authenticated" 200
	}
	respond "Not Authenticated" 403
}

Basic

## Success - Correct credentials
$ curl http://localhost -H 'X-Client-ID: 1' -u 'Bob:hiccup' -v
* Host localhost:80 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:80...
* Connected to localhost (::1) port 80
* using HTTP/1.x
* Server auth using Basic with user 'Bob'
> GET / HTTP/1.1
> Host: localhost
> Authorization: Basic Qm9iOmhpY2N1cA==
> User-Agent: curl/8.14.1
> Accept: */*
> X-Client-ID: 1
>
* Request completely sent off
< HTTP/1.1 200 OK
< Content-Length: 12
< Content-Type: text/plain; charset=utf-8
< Date: Wed, 25 Jun 2025 14:44:46 GMT
< Server: Caddy
< Via: 1.1 Caddy
<
* Connection #0 to host localhost left intact
Welcome, Bob
## Failure - Incorrect credentials
$ curl http://localhost -H 'X-Client-ID: 1' -u 'Random:user' -v
* Host localhost:80 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:80...
* Connected to localhost (::1) port 80
* using HTTP/1.x
* Server auth using Basic with user 'Random'
> GET / HTTP/1.1
> Host: localhost
> Authorization: Basic UmFuZG9tOnVzZXI=
> User-Agent: curl/8.14.1
> Accept: */*
> X-Client-ID: 1
>
* Request completely sent off
< HTTP/1.1 401 Unauthorized
< Content-Length: 0
< Date: Wed, 25 Jun 2025 14:45:12 GMT
< Server: Caddy
< Via: 1.1 Caddy
* Basic authentication problem, ignoring.
< Www-Authenticate: Basic realm="restricted"
<
* Connection #0 to host localhost left intact

Bearer

## Success - Bearer Auth present
$ curl http://localhost -H 'X-Client-ID: 2' -H 'Authentication: Bearer 123' -v
* Host localhost:80 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:80...
* Connected to localhost (::1) port 80
* using HTTP/1.x
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/8.14.1
> Accept: */*
> X-Client-ID: 2
> Authentication: Bearer 123
>
* Request completely sent off
< HTTP/1.1 200 OK
< Content-Length: 13
< Content-Type: text/plain; charset=utf-8
< Date: Wed, 25 Jun 2025 14:47:02 GMT
< Server: Caddy
< Via: 1.1 Caddy
<
* Connection #0 to host localhost left intact
Authenticated
## Failure - Bearer Auth absent
$ curl http://localhost -H 'X-Client-ID: 2' -v
* Host localhost:80 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:80...
* Connected to localhost (::1) port 80
* using HTTP/1.x
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/8.14.1
> Accept: */*
> X-Client-ID: 2
>
* Request completely sent off
< HTTP/1.1 403 Forbidden
< Content-Length: 17
< Content-Type: text/plain; charset=utf-8
< Date: Wed, 25 Jun 2025 14:47:44 GMT
< Server: Caddy
< Via: 1.1 Caddy
<
* Connection #0 to host localhost left intact
Not Authenticated

I think your back-end server might not be properly processing the header.

Try to enable debug or log, and you should see in your logs something like this for the Basic Authentication:

..., "headers": {"Authorization": ["REDACTED"], ...

or this for the Bearer Authentication:

..., "Authentication": ["Bearer 123"], ...

If you see that, it means Caddy receives it from the client and passes it to the upstream.

1 Like

Thanks for the feedback and the snippets!

Upon further investigation we found out that the client was calling the HTTP:80 port, caddy correctly answered with the redirect to HTTPS:443 and the client after following the redirect didn’t re-transmit the full request. Sensitive data like the Authorization header were stripped client side after the redirect to HTTPS.

3 Likes