Alright, it took me a while, but I have an explanation!
First, you have to know that Caddy passes the original Host
header to upstream instead of using the upstream’s own address in the upstream request. This is critical to understand what’s happening here.
Second, the behavior of the Go runtime when it comes to handling the HTTP_PROXY
address includes this caveat:
As a special case, if req.URL.Host is “localhost” (with or without a port number), then a nil URL and nil error will be returned.
Meaning, it checks if the dialed URL (the upstream, not the proxy) is localhost
, 127.x.x.x
, or the IPv6 representation of loopback; and if the upstream URL is any of those, the Go runtime will not pass it through the URL specified in HTTP_PROXY
. This is why these workarounds work
But why is the issue happening in the first place?
Well, with this config…
… this is the request journey:
1- Caddy receives a request on localhost:80
2- The dialed upstream address is 0.0.0.0:8080
, so the Go runtime uses the URL specified in the HTTP_PROXY
.
3- Caddy passes the original Host
header
4- The proxy server receives the request with the value of the Host
being localhost:80
, per Caddy’s default behavior
5- The proxy server calls the service specified in the Host
header, which is localhost:80
, which is Caddy
6- Go back to 1.
My point to Francis still stands. The address 0.0.0.0
works by accident. It’s best to be explicit, especially now that we’ve seen the complexity it entails, but that could be me