Caddy behind a reverse proxy use wrong X-Forwarded-Proto

1. Caddy version (caddy version):

v2.5.1 h1:bAWwslD1jNeCzDa+jDCNwb8M3UJ2tPa8UZFFzPVmGKs=

2. How I run Caddy:

As a reverse proxy and web server for a symfony app

a. System environment:

Docker desktop in WSL 2

Dockerfile

ARG CADDY_VERSION=2

FROM caddy:${CADDY_VERSION}-builder-alpine AS symfony_caddy_builder

RUN xcaddy build \
	--with github.com/dunglas/mercure \
	--with github.com/dunglas/mercure/caddy \
	--with github.com/dunglas/vulcain \
	--with github.com/dunglas/vulcain/caddy

FROM caddy:${CADDY_VERSION} AS symfony_caddy

WORKDIR /srv/app

COPY --from=dunglas/mercure:v0.11 /srv/public /srv/mercure-assets/
COPY --from=symfony_caddy_builder /usr/bin/caddy /usr/bin/caddy
COPY --from=symfony_php /srv/app/public public/
COPY docker/caddy/Caddyfile /etc/caddy/Caddyfile

docker-compose.yml

caddy:
    build:
      context: .
      target: symfony_caddy
    depends_on:
      - php
    environment:
      SERVER_NAME: ${SERVER_NAME:-localhost}
      MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeMe!}
      MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeMe!}
    restart: unless-stopped
    volumes:
      - php_socket:/var/run/php
      - caddy_data:/data
      - caddy_config:/config
    ports:
      # HTTP
      - target: 80
        published: ${HTTP_PORT:-80}
        protocol: tcp
      # HTTPS
      - target: 443
        published: ${HTTPS_PORT:-443}
        protocol: tcp
      # HTTP/3
      - target: 443
        published: ${HTTP3_PORT:-443}
        protocol: udp

b. Command:

none

c. Service/unit/compose file:

Paste full file contents here.
Make sure backticks stay on their own lines,
and the post looks nice in the preview pane.

d. My complete Caddyfile or JSON config:

{
    # Debug
    {$DEBUG}
    # HTTP/3 support
    servers {
        protocol {
            experimental_http3
        }
    }
}

{$SERVER_NAME}:443 {
    log

    reverse_proxy {$SERVER_NAME}:80
}


{$SERVER_NAME}:80, caddy:80 {
    log

    route {
        root * /srv/app/public
        mercure {
            # Transport to use (default to Bolt)
            transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
            # Publisher JWT key
            publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
            # Subscriber JWT key
            subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
            # Allow anonymous subscribers (double-check that it's what you want)
            anonymous
            # Enable the subscription API (double-check that it's what you want)
            subscriptions
            # Extra directives
            {$MERCURE_EXTRA_DIRECTIVES}
        }
        vulcain
        push
        php_fastcgi unix//var/run/php/php-fpm.sock
        encode zstd gzip
        file_server
    }
}

3. The problem I’m having:

Caddy send X-Forwarded-Proto with an http value to my symfony app.
The web server part of the Caddyfile don’t forward the proto of the reverse proxy section like nginx do.

4. Error messages and/or full log output:

{"level":"info","ts":1657098944.7389662,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"172.22.0.4","remote_port":"46952","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/","headers":{"Sec-Fetch-User":["?1"],"Upgrade-Insecure-Requests":["1"],"X-Forwarded-For":["172.22.0.1"],"X-Forwarded-Proto":["https"],"Accept-Language":["fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7"],"Cache-Control":["max-age=0"],"Sec-Fetch-Site":["cross-site"],"Sec-Fetch-Dest":["document"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Mode":["navigate"]}},"user_id":"","duration":0.070867548,"size":58191,"status":200,"resp_headers":{"Date":["Wed, 06 Jul 2022 09:15:44 GMT"],"X-Debug-Token":["b39101"],"Content-Type":["text/html; charset=UTF-8"],"Server":["Caddy"],"Cache-Control":["no-cache, private"],"X-Debug-Token-Link":["http://localhost/_profiler/b39101"],"X-Robots-Tag":["noindex"],"Link":["<http://localhost/api/docs.jsonld>; rel=\"http://www.w3.org/ns/hydra/core#apiDocumentation\""],"X-Powered-By":["PHP/8.1.7"]}}
{"level":"info","ts":1657098944.7403917,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"172.22.0.1","remote_port":"33870","proto":"HTTP/2.0","method":"GET","host":"localhost","uri":"/","headers":{"Cache-Control":["max-age=0"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br"],"Upgrade-Insecure-Requests":["1"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"],"Sec-Gpc":["1"],"Sec-Fetch-Site":["cross-site"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-User":["?1"],"Sec-Fetch-Dest":["document"],"Accept-Language":["fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"localhost"}},"user_id":"","duration":0.075352902,"size":58191,"status":200,"resp_headers":{"Alt-Svc":["h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000"],"Date":["Wed, 06 Jul 2022 09:15:44 GMT"],"Link":["<http://localhost/api/docs.jsonld>; rel=\"http://www.w3.org/ns/hydra/core#apiDocumentation\""],"X-Robots-Tag":["noindex"],"Cache-Control":["no-cache, private"],"Server":["Caddy","Caddy"],"X-Debug-Token-Link":["http://localhost/_profiler/b39101"],"X-Debug-Token":["b39101"],"Content-Type":["text/html; charset=UTF-8"],"X-Powered-By":["PHP/8.1.7"]}}
{"level":"error","ts":1657098945.2216153,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"172.22.0.1","remote_port":"46794","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/_wdt/b39101","headers":{"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["cross-site"],"Connection":["keep-alive"],"Accept":["*/*"],"Access-Control-Request-Method":["GET"],"Access-Control-Request-Headers":["x-requested-with"],"Origin":["https://localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36"],"Accept-Language":["fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7"],"Sec-Fetch-Dest":["empty"],"Accept-Encoding":["gzip, deflate, br"]}},"user_id":"","duration":0.052704014,"size":36,"status":400,"resp_headers":{"Server":["Caddy"],"X-Debug-Token-Link":["http://localhost/_profiler/413d67"],"X-Powered-By":["PHP/8.1.7"],"Vary":["Origin"],"Access-Control-Allow-Methods":["GET, OPTIONS, POST, PUT, PATCH, DELETE"],"Access-Control-Allow-Origin":["https://localhost"],"X-Debug-Token":["413d67"],"Access-Control-Max-Age":["3600"],"Cache-Control":["no-cache, private"],"Content-Type":["text/html; charset=UTF-8"],"Access-Control-Allow-Headers":["content-type, authorization"],"Date":["Wed, 06 Jul 2022 09:15:45 GMT"],"Status":["400 Bad Request"],"X-Robots-Tag":["noindex"]}}

5. What I already tried:

I already tried to force value with header_up in the reverse proxy and header in the web server.

6. Links to relevant resources:

As of v2.5.0, you need to configure trusted_proxies for Caddy’s reverse_proxy to trust incoming X-Forwarded-* headers, otherwise it will ignore them and use values it knows it can trust, i.e. what the request looks like from its own perspective.

See the reverse_proxy headers docs:

This applies to php_fastcgi as well, which is just syntax sugar over reverse_proxy but using the fastcgi transport instead of the http transport which is the default (plus some extra stuff for file/path handling)

Thanks for your response.

Just adding trusted_proxies private_ranges solved my problem.

php_fastcgi unix//var/run/php/php-fpm.sock {
    trusted_proxies private_ranges
}
2 Likes

This topic was automatically closed after 30 days. New replies are no longer allowed.