Caddy2 w/Wordpress behind nginx reverse proxy

I have a public-facing nginx handling ssl reverse proxying to an internal nginx server that I’d like to replace with Caddy.

Internet->https://nginx proxy/cache->http://internalip:80

Caddyfile (on internal server):

http://example.com:80
    {
            root * /srv/webroot/example.com
            log {
                    output file /var/log/caddy/example-access.log
                    format console
            }

            php_fastcgi unix//run/php/php-fpm.sock
            file_server
    }

The connection is functioning and Caddy isn’t trying to get an SSL cert since I specified http specifically for the matcher.
The problem is Caddy doesn’t seem to be passing along the X-Forwarded-Proto header coming from the nginx proxy as-is to Wordpress. Wordpress thinks it’s being accessed by http and the assets on the site fail to load because Wordpress used http links to them causing a mixed content error in the browser.

(index):16 Mixed Content: The page at 'https://example.com/' was loaded over HTTPS, but requested an insecure script 'http://example.com/wp-includes/js/wp-emoji-release.min.js?ver=5.4'. This request has been blocked; the content must be served over HTTPS.

Trying to access the login/admin pages sends it into a redirect loop so they won’t load.
I confirmed this by sticking
echo "HTTP-X-FORWARDED-PROTO: " . $_SERVER['HTTP_X_FORWARDED_PROTO'];
in wp-config.php just to see what the site thought it was getting and it always returns “HTTP-X-FORWARDED-PROTO: http”
Caddy’s access log shows the request coming from the nginx proxy with X-Forwarded-Proto set to https like it should be.
Is there a way I can get Caddy to pass that header along un-touched?
Is there a more appropriate way I should be setting this up? Caddy is on an internal server so it can’t do the letsencrypt dance and the nginx proxy already has the letsencrypt cert for the actual domain name.

Oh, that’s interesting. I think that might be a bug we should fix @matt, I think we should make it respect the incoming header if it exists and not overwrite it https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/reverseproxy.go#L444

But for the time being, @plastrd I think you can do this:

php_fastcgi unix//run/php/php-fpm.sock {
    header_up X-Forwarded-Proto https
}

X-Forwarded-Proto is a hop-by-hop header; we are supposed to overwrite it at every hop, since it represents the protocol used at the last hop.

But since it’s not standardized, there’s no formal rule about it… all I can find is this: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto

So the user should probably configure that header explicitly if it needs to be a certain value.

I don’t think that makes sense. Shouldn’t it always be the proto used at the first hop, i.e. client to load-balancer/proxy?

Everything I’m seeing says it should be the “originating protocol”.

I was racking my brain over trying to figure out how to make the standard header directive pass this along instead of just adding it as a response back to the browser. I didn’t realize header_up would work like this under php_fastcgi but it instantly solved all issues with the site, thank you.

@matt This is kind of a unique situation so I could see it interpreted different ways.
To determine the protocol used between the client and the load balancer, the X-Forwarded-Proto request header can be used.
In a literal sense it would say the only hop that matters is the one from my browser to the nginx proxy (load balancer) so it would be kept the same through all the remaining hops.

@plastrd FYI Caddy v2.1 (not yet release) should stop overriding X-Forwarded-Proto if set by another proxy: