Real IP in Docker reverse proxy

1. Caddy version (caddy version):

Caddy v2

2. How I run Caddy:

Caddy is running inside a docker service

a. System environment:

Ubuntu 20

b. Command:

docker service create --network personalnetwork image:tag

c. Service/unit/compose file:

docker service create --network personalnetwork image:tag

that image has caddy on it build for linux and running as

./caddy run

d. My complete Caddyfile or JSON config:

tls internal {

header {
        # Disallow the site to be rendered within a frame on different domain (clickjacking protection)
        +X-Frame-Options "SAMEORIGIN"


        +Access-Control-Allow-Headers Range
        +Access-Control-Expose-Headers "Accept-Ranges, Content-Encoding, Content-Length, Content-Range"

@allowedIPs {

redir /secure /secure/
route /secure/* {
        header X-Forwarded-For {http.matchers.remote_ip}
        header X-Real-IP {}
        uri strip_prefix /secure
        reverse_proxy @allowedIPs http://secure_docker_container:8888

3. The problem I’m having:

The ip in docker is not the user ip but the docker gateway

4. Error messages and/or full log output:

no errors

5. What I already tried:

header X-Forwarded-For {http.matchers.remote_ip}
header X-Real-IP {}

6. Links to relevant resources:

I tried adding the service to the host network bu this still is not working

You can replace this with just handle_path /secure/* instead, which has implicit strip_prefix logic.

You don’t need this at all, Caddy sets it implicitly:

But anyways, header changes the response headers, not request headers.

And the placeholder you’re using here doesn’t make sense. http.matchers.remote_ip is a module name, not a placeholder.

This also doesn’t make sense for similar reasons, but also that placeholder isn’t set until the reverse_proxy handler is executing. If you were to use it in the right place though, i.e. a header_up inside of the reverse_proxy this placeholder would have the value secure_docker_container, i.e. the hostname of the upstream you configured, which is not an IP address.

Ultimately, this isn’t really a problem with Caddy, but rather an issue with Docker. Caddy is not aware of the remote IP because when running with Docker Swarm (as you are, as evidenced by your docker service command), Docker has an ingress proxy which sits between the client and Caddy.

If the ingress proxy supports the PROXY protocol though, you may use the GitHub - mastercactapus/caddy2-proxyprotocol plugin to resolve the issue. You can find docs on configuring listener wrappers here in the docs:

Thank you for your answer. I’ll have a look

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