Caddy 2.11 HTTP 403 error via docker exec caddy reload

1. The problem I’m having:

We utilise caddy running a docker image as an ingress service router for our platform. As part of the update process, our CI/CD pipeline (Gitlab CI/CD) connects to the docker host, refreshes the project and then will run ‘caddy reload’ to ensure up to date config is loaded.
This is is done via:

docker exec -w /etc/caddy ingress-router-caddy-1 caddy reload

This has worked without issue on Caddy v2.10. After updating the image to Caddy v2.11.1 this morning, we now see the following problem:

$ docker exec -w /etc/caddy ingress-router-caddy-1 caddy reload
{"level":"info","ts":1772070489.923556,"msg":"using adjacent Caddyfile"}
{"level":"warn","ts":1772070489.945869,"msg":"The nested 'metrics' option inside `servers` is deprecated and will be removed in the next major version. Use the global 'metrics' option instead."}
{"level":"info","ts":1772070489.9707046,"msg":"adapted config to JSON","adapter":"caddyfile"}
Error: sending configuration to instance: caddy responded with error: HTTP 403: {"error":"client is not allowed to access from origin 'http://localhost:2019'"}

This occus both manually and in the Gitlab CI/CD pipeline.

2. Error messages and/or full log output:

$ docker exec -w /etc/caddy ingress-router-caddy-1 caddy reload
{"level":"info","ts":1772070489.923556,"msg":"using adjacent Caddyfile"}
{"level":"warn","ts":1772070489.945869,"msg":"The nested 'metrics' option inside `servers` is deprecated and will be removed in the next major version. Use the global 'metrics' option instead."}
{"level":"info","ts":1772070489.9707046,"msg":"adapted config to JSON","adapter":"caddyfile"}
Error: sending configuration to instance: caddy responded with error: HTTP 403: {"error":"client is not allowed to access from origin 'http://localhost:2019'"}

3. Caddy version:

$ docker exec -w /etc/caddy ingress-router-caddy-1 caddy version
v2.11.1 h1:C7sQpsFOC5CH+31KqJc7EoOf8mXrOEkFyYd6GpIqm/s=

4. How I installed and ran Caddy:

Installed and run as a custom docker image, using caddy:2.11-alpine as the base image

a. System environment:

OS sersion: Ubuntu 24.04.4 LTS
using Docker version: 29.2.1, build a5c7197

b. Command:

docker compose up -d --wait

c. Service/unit/compose file:

name: ingress-router

services:
  # base 'caddy' app router
  caddy:
    image: $INTERNAL_REG_REMOVED/caddy:2.11-alpine
    restart: unless-stopped
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "5"
        compress: "true"
    ports:
      - 80:80
      - 443:443
      - 2019:2019
    networks:
      - caddy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - caddy_data:/data
      - caddy_config:/config
      - /etc/caddy:/etc/caddy
      - /var/log/caddy:/var/log/caddy
   
networks:
  caddy:
    external: true

volumes:
  caddy_data: {}
  caddy_config: {}

d. My complete Caddy config:

{
        admin 0.0.0.0:2019
        email operations@example.com
        log access-json {
                format json
                include http.log.access.access-logs
                output file /var/log/caddy/access-json.log
                format filter {
                        request>headers>Cookie delete
                        resp_headers delete
                }
        }
        log access-common {
                format transform `{request>headers>X-Forwarded-For>[0]:request>remote_ip} - {user_id} [{ts}] {request>headers>X-Forwarded-Host:request>host} "{request>method} {request>uri} {request>proto}" {status} {size} "{request>headers>Referer>[0]}" "{request>headers>User-Agent>[0]}"` {
                        time_format iso8601
                }
                include http.log.access.access-logs
                output file /var/log/caddy/access-common.log
        }
        servers {
                metrics
        }
}
(base-rm-resp-headers) {
        header -server
        header -x-commerce-core
        header -x-drupal-cache
        header -x-drupal-dynamic-cache
        header -x-generator
        header -x-powered-by
}
(base-transport-tls-insecure) {
        transport http {
                tls_insecure_skip_verify
        }
}
(base-transport-tls-insecure-and-sni) {
        transport http {
                tls_insecure_skip_verify
                tls_server_name {host}
        }
}

import Caddyfile.d/*.caddyfile

5. Links to relevant resources:

N/A

Possibly a bug, since the admin enforce_origin seems to blocking your request, but is documented as off by default. Global options (Caddyfile) — Caddy Documentation

Yes, I’ve had to manually add the following to allow ‘caddy reload’ via docker exec to work:

admin :2019 {origins localhost localhost:2019}

There’s a new simpler way to do config reloads in v2.11 as well:

docker compose kill -sUSR1 caddy

Or without compose

docker kill -sUSR1 ingress-router-caddy-1

Docs: Command Line — Caddy Documentation

2 Likes

Ah, okay, so caddy reload sets the Origin header if it’s not a Unix socket or file descriptor. I am trying to remember why we do that, because the new code only expects browsers to do that… Stay tuned. Maybe some tweaking needed.

1 Like

Thanks @matt , would you like me to raise this in GitHub · Where software is built ? Or is this thread suffiicent?

I’m already halfway into fixing it, but an issue would be a good idea so I don’t lose track of it in case I get sidetracked.

No problem: https://github.com/caddyserver/caddy/issues/7528

1 Like