Using different reverse proxy URL based on site definitions including a wildcard

Hi there!

1. Caddy version (caddy version):

2.3.0

2. How I run Caddy:

a. System environment:

Docker version 20.10.2 on Debian 10.8.

b. Command:

docker rmi -f caddy > /dev/null
docker build -t caddy:latest .
docker run -d \
    --log-opt max-size=10m \
    --log-opt max-file=5 \
    --memory="3g" \
    --name=acme-caddy \
    --restart=unless-stopped \
    -e ACME_CADDY_DATABASE_DB=$ACME_CADDY_DATABASE_DB \
    -e ACME_CADDY_DATABASE_HOST=$ACME_CADDY_DATABASE_HOST \
    -e ACME_CADDY_DATABASE_PASSWORD=$ACME_CADDY_DATABASE_PASSWORD \
    -e ACME_CADDY_DATABASE_PORT=$ACME_CADDY_DATABASE_PORT \
    -e ACME_CADDY_DATABASE_USERNAME=$ACME_CADDY_DATABASE_USERNAME \
    -p 80:80 \
    -p 443:443 \
    -v $PWD/Caddyfile:/etc/caddy/Caddyfile \
    caddy:latest

c. Service/unit/compose file:

Here is the Dockerfile:

FROM caddy:2.3.0-builder AS builder

RUN xcaddy build \
    --with github.com/gamalan/caddy-tlsredis

FROM caddy:2.3.0

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

d. My complete Caddyfile or JSON config:

{
    email "support@acme.com"
    on_demand_tls {
        ask "https://service.acme.com/newspages"
        burst 60
        interval 1m
    }
    storage redis {
        host          "{$ACME_CADDY_DATABASE_HOST}"
        port          "{$ACME_CADDY_DATABASE_PORT}"
        username      "{$ACME_CADDY_DATABASE_USERNAME}"
        password      "{$ACME_CADDY_DATABASE_PASSWORD}"
        db            {$ACME_CADDY_DATABASE_DB}
        key_prefix    "caddy"
        value_prefix  "caddy-storage"
        timeout       10
        tls_enabled   "false"
        tls_insecure  "true"
    }
}

*.staging.acme.news {
    encode zstd gzip
    reverse_proxy https://acme-newspage-ovj16u24ja-uc.a.run.app {
        fail_duration 55s
        header_up host {http.reverse_proxy.upstream.hostport}
        header_up +acme-domain {http.request.host}
        header_down -server
    }
    tls {
        on_demand
    }
}

:443 {
    encode zstd gzip
    reverse_proxy https://acme-newspage-vasm4c6eca-uc.a.run.app {
        fail_duration 55s
        header_up host {http.reverse_proxy.upstream.hostport}
        header_up +acme-domain {http.request.host}
        header_down -server
    }
    tls {
        on_demand
    }
}

3. The problem I’m having:

As you can see in the Caddyfile, I have 2 site definitions, one for *.staging.acme.news and a fallback :443.

If I open https://test.acme.news, then the definition for :443 is used and everything works. However, if I open https://test.staging.acme.news, then the site configuration that is used is still :443 whereas I was expecting *.staging.acme.news to be picked.

What I would like is to use a different reverse_proxy URL based on whether Caddy receives a request from *.staging.acme.news or *.acme.news.

4. Error messages and/or full log output:

No error message. The problem is about site definition.

5. What I already tried:

I tried changing the order of the definitions and replacing :443 by *.acme.news:443 but in this last case, even https://test.acme.news is no longer working.

Does :443 have priority over all definitions whatever its place is in the Caddyfile?

6. Links to relevant resources:

That’s strange. Could you adapt your config to JSON with caddy adapt --pretty (you can run it in the container with docker exec acme-caddy -w /etc/caddy caddy adapt --pretty) and show the output? I’d like to see the order of the handlers in the JSON config.

It’s very possible this bug has already been fixed on the master branch of Caddy, there were a few edgecase bugs with Caddyfile ordering, and this may have been one of them.

You can try building from the latest commit with xcaddy build 0d7fe36007d5fbadaa6057b6543500d63147e6c0, i.e. specify the commit hash right after the build command.

I think in particular, it was fixed by this commit:

All that said, another way you could structure your config is like this:

{
	... globals
}

:443 {
	map {host} {upstream} {
		~(.*)\.staging.acme.news  acme-newspage-ovj16u24ja-uc.a.run.app:443
		default                   acme-newspage-vasm4c6eca-uc.a.run.app:443
	}

	encode zstd gzip
	reverse_proxy {upstream} {
		transport http {
			tls
		}
		fail_duration 55s
		header_up host {http.reverse_proxy.upstream.hostport}
		header_up +acme-domain {http.request.host}
		header_down -server
	}
	tls {
		on_demand
	}
}

This makes use of map (Caddyfile directive) — Caddy Documentation to make the decision based on the incoming host, with a default fallback. (Note that there was also another bug with this specific pattern that was fixed in commit reverseproxy: Fix upstreams with placeholders with no port (#4046) · caddyserver/caddy@51f35ba · GitHub so I recommend using the latest on master, i.e. edb362aa96bd8e79adfaca44fbd9f9ce5bff778d to get all the latest fixes)

1 Like

Thanks a lot for your help.

Here is the requested file:

Using Caddy built from master along with your config example works perfectly. That’s a nice configuration factorization. Thanks again!

1 Like

Yeah that confirms it. You can see in the config that this part:

              "match": [
                {
                  "host": [
                    "*.staging.acme.news"
                  ]
                }
              ],

Is the second route, i.e. it wasn’t sorted correctly to be after the catch-all route.

Excellent :smiley:

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