How to redirect every subdomain except a specific one?

1. Output of caddy version:

2.5.1 - Alpine Docker image

2. How I run Caddy:

a. System environment:

Docker running on Ubuntiu 22.04.

b. Command:

docker compose up

c. Service/unit/compose file:

services:
  client:
    image: client-production:${ENV}
    restart: unless-stopped
    ports:
      - '80:80'
      - '443:443'
    networks:
      - mern-application
    volumes:
      - caddy-data:/data
      - caddy-config:/config

  server:
    image: server
    restart: unless-stopped
    ports:
      - '8081:8081'
    networks:
      - mern-application

networks:
  mern-application:
    driver: bridge
volumes:
  caddy-data:
    driver: local
  caddy-config:
    driver: local

d. My complete Caddy config:

example.com:443 {
    tls email@gmail.com
    root * /srv
    route {
        reverse_proxy /api* server:8081
        try_files {path} {path}/ /index.html
        file_server
    }
}

*.example.com {
    redir https://example.com{uri}
}

3. The problem I’m having:

I would like to achieve the following:

admin.example.com - no redirect
xyz.example.com - redirect to example.com

5. What I already tried:

This approach worked for all subdomains:

*.example.com {
    redir https://example.com{uri}
}

Based on the documentation this seems to be the right direction, but I am stuck:

*.example.com {
    @subdomains {
     //somehow exclude the admin subdomain?
    }

    redir @subdomains https://example.com{uri}
}

What does “no redirect” mean, exactly? What do you want to do instead?

Please fill out the help topic template, as per the forum rules.

Hi, sure, I’ll do it - but it’s more of a syntactical question based on the documentation. Under no redirect I mean that I want to use the admin.example.com subdomain on my site, so I don’t want it to be redirected to example.com, unlike all other subdomains, that I don’t use. So to answer your questions precisely: I don’t want to do anything instead.

Maybe try

@notAdmin not vars {labels.2} admin
redir @notAdmin https://example.com{uri}

Probably a bit simpler:

@notAdmin not host admin.example.com
redir @notAdmin https://example.com{uri}

The point I was trying to make though, is that entirely depends on what else is in your config, because the Caddyfile adapter sorts directives according to a particular order:

You need other config in there to do something with requests to admin.example.com (like, proxy the request, or serve static files, etc), and how you choose to do that might affect if the redirects work correctly.

If you don’t otherwise handle requests to admin.example.com, then Caddy will just respond with an empty 200 status response, because it wasn’t configured to do anything else.

Please upgrade to v2.5.2, the latest version.

You don’t need to specify :443 here, because that’s the default; Caddy is HTTPS by default.

I would instead write it like this:

	handle /api* {
		reverse_proxy server:8081
	}

	handle {
		root * /srv
		try_files {path} {path}/ /index.html
		file_server
	}

This way, the proxy routes are clearly separated from the static files config, and it’s a bit easier to see what will happen as it runs.

You don’t need this btw, only Caddy needs access to connect to your backend app. Publishing the port to the host can be a risk, because if you accidentally open it up to the public web, then people could skip using HTTPS, or skip any kind of auth or whatever you might later have Caddy do for you.

2 Likes

Thank you very much for your answer, it helped me a lot. I have modified my caddyfile based on your suggestions:

example.com, admin.example.com {
    tls email@gmail.com
	
    handle /api* {
		reverse_proxy server:8081
	}

	handle {
		root * /srv
		try_files {path} {path}/ /index.html
		file_server
	}
}

*.example.com {
    @subdomains not host admin.example.com
    redir @subdomains https://example.com{uri}
}

*.admin.example.com {
    redir https://admin.example.com{uri}
}

but it seems something is still missing, because although the domains example.com and admin.example.com are working, any other subdomain throws a similar error:

client_1  | {"level":"error","ts":1663421768.9444897,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"*.example.com","issuer":"acme.zerossl.com-v2-DV90","error":"[*.example.com] solving challenges: *.example.com: no solvers available for remaining challenges (configured=[http-01 tls-alpn-01] offered=[dns-01] remaining=[dns-01]) (order=https://acme.zerossl.com/v2/DV90/order/GBBsxaWs3wkx_vF0RA3Bog) (ca=https://acme.zerossl.com/v2/DV90)"}

You will need to configure the DNS challenge if you want to use wildcard certificates

See docs/automatic-https#wildcard-certificates and docs/caddyfile/patterns#wildcard-certificates

You could also use Caddy’s On-Demand certificates instead, which is explained in docs/automatic-https#on-demand-tls

2 Likes

Thanks for the idea, I added on-demand certificates like this:

tls {
    on_demand
}

It seems to be working, but Chrome gives me the “the connection is not private” screen and I’m not being redirected.

client_1  | {"level":"info","ts":1663432210.1971653,"logger":"tls.on_demand","msg":"obtaining new certificate","server_name":"xyz.example.com"}
client_1  | {"level":"info","ts":1663432210.1988943,"logger":"tls.obtain","msg":"acquiring lock","identifier":"xyz.example.com"}
client_1  | {"level":"info","ts":1663432210.2036602,"logger":"tls.obtain","msg":"lock acquired","identifier":"xyz.example.com"}
client_1  | {"level":"info","ts":1663432210.2059684,"logger":"tls.issuance.acme","msg":"waiting on internal rate limiter","identifiers":["xyz.example.com"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":"email@gmail.com"}
client_1  | {"level":"info","ts":1663432210.2067494,"logger":"tls.issuance.acme","msg":"done waiting on internal rate limiter","identifiers":["xyz.example.com"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":"email@gmail.com"}
client_1  | {"level":"info","ts":1663432210.899205,"logger":"tls.issuance.acme.acme_client","msg":"trying to solve challenge","identifier":"xyz.example.com","challenge_type":"tls-alpn-01","ca":"https://acme-v02.api.letsencrypt.org/directory"}
client_1  | {"level":"info","ts":1663432211.313457,"logger":"tls","msg":"served key authentication certificate","server_name":"xyz.example.com","challenge":"tls-alpn-01","remote":"3.125.50.156:40102","distributed":false}
client_1  | {"level":"info","ts":1663432211.342081,"logger":"tls","msg":"served key authentication certificate","server_name":"xyz.example.com","challenge":"tls-alpn-01","remote":"3.16.81.252:12244","distributed":false}
client_1  | {"level":"info","ts":1663432211.452645,"logger":"tls","msg":"served key authentication certificate","server_name":"xyz.example.com","challenge":"tls-alpn-01","remote":"54.189.198.223:42978","distributed":false}
client_1  | {"level":"info","ts":1663432211.4558823,"logger":"tls","msg":"served key authentication certificate","server_name":"xyz.example.com","challenge":"tls-alpn-01","remote":"23.178.112.107:63026","distributed":false}
client_1  | {"level":"info","ts":1663432211.898402,"logger":"tls.issuance.acme.acme_client","msg":"validations succeeded; finalizing order","order":"https://acme-v02.api.letsencrypt.org/acme/order/735757851/126375986891"}
client_1  | {"level":"info","ts":1663432213.1983168,"logger":"tls.issuance.acme.acme_client","msg":"successfully downloaded available certificate chains","count":2,"first_url":"https://acme-v02.api.letsencrypt.org/acme/cert/03aa07a1cda836718b79488ac6124f9f654f"}
client_1  | {"level":"info","ts":1663432213.1999633,"logger":"tls.obtain","msg":"certificate obtained successfully","identifier":"xyz.example.com"}
client_1  | {"level":"info","ts":1663432213.2011125,"logger":"tls.obtain","msg":"releasing lock","identifier":"xyz.example.com"}

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

Still have not found a working solution, please let me know if you have any ideas :pray:

Probably because you’ve redacted your domains and are using browsers instead of curl -v as the help template requires :frowning: So we can’t help you…

What are your actual domains/certificates and what is the output of curl -v?

Thanks. So… it’s working now as desired?

Dunno then. That loads for me in both curl -v and Firefox without errors. :man_shrugging: Maybe an issue with your Chrome installation?