On-demand SSL: redirect only apex domains to "www.", but keep other subdomains as-is

1. The problem I’m having:

I’m working on a service that’s supposed to serve websites for dozens/hundreds of domains and subdomains, using on-demand SSL to make the whole setup simpler. I want to keep the config file as generic as possible, so I don’t want to create a config entry for every domain/subdomain we host. I want to just tell a client to redirect their domain/subdomain/www to my IP and their website should magically work.

To make it simple:

  • Some websites will run on subdomains (ex, products.customer.com)
  • Some websites will run on the apex domain (ex, customer.com) and these will need a redirect to www.customer.com

I need to be able to detect whether the hostname of a request is a naked/apex domain (customer.com) or if it is a subdomain from a domain (products.customer.com). I thought of using regexes on {host} but TLDs can vary a lot (foo.com, foo.co.uk, foo.travel, etc - there are 1,502 TLDs right now). Can’t use {labels} either for the same reason.

The logic I need is:

  • If the request is for a subdomain (and the subdomain is not “www.”), I serve it.
  • If the request is for a “www.” subdomain (easy to detect, just check whether {host} begins with www.), I serve it.
  • If the request is for a naked/apex domain without subdomains, I redirect to “www.{host}{uri}”.

It would be easy to manually create configs for each individual domain/subdomain case, but I wanted to avoid that and have a very generic config file that I’ll never have to touch again (lol).

How can I express that?

One thought I had was to extend Caddy to support a new placeholder {domain} ({http.request.domain}) in addition to {host}, where {domain} is parsed by github.com/weppos/publicsuffix-go and would represent just the domain part of {host} regardless of TLD (assuming it’s not an IP, etc).

Then I can just check whether {host} equals {domain}, and if true, that means I’m serving the naked/apex domain (so I can redirect to www.{host}{uri}).

Is there already a way to achieve what I need without having to mod Caddy?

Thanks!

Caddy version:

v2.7.6

My complete Caddy config:

(logger) {
        log {
                format json
                # output file /logs/{args[0]}-{args[1]}.log {
                output file /logs/{args[0]}.log {
                        roll_size 1gb
                        roll_keep 5
                        roll_keep_for 720h
                }
        }
}

{
        servers {
                max_header_size 4kb
                timeouts {
                        read_body 10s
                        read_header 10s
                        write 10s
                        idle 1m
                }
        }

        on_demand_tls {
                ask https://127.0.0.1/validate-domain
                interval 1s
                burst 5
        }

        debug
        storage file_system /var/lib/caddy/storage
        persist_config off

        log {
                format json
                level DEBUG
                output file /logs/caddy.log {
                        roll_size 1gb
                        roll_keep 5
                        roll_keep_for 720h
                }
        }
}

:443 {
        import logger webengine
        tls dansouza@mail.io {
                resolvers 8.8.8.8 8.8.4.4
                on_demand
        }
        encode gzip zstd
        root * /www/webengine/webengine-root

        @static_files {
                path *.gif *.jpg *.jpeg *.png *.js *.css *.eot *.ttf *.woff *.woff2
        }

        route @static_files {
                file_server
        }

        php_fastcgi unix///run/php/php8.1-fpm.sock {
                env CONFIG_NAME cluster-production
                root /www/webengine/webengine-root
                try_files /index.php
                index index.php
        }
}

FWIW, I wrote a public suffix module a while ago here, you can use; it creates placeholders kind of like what you’re talking about:

1 Like

Yeah – I don’t see a way around using PSL for this. If you were doing it the other way around with www. → apex, it’s super easy. But there’s no reliable way to go in the other direction when there’s also some domains you don’t want to redirect.

1 Like

Thank you @matt, looks like {header.Host.registered_domain} is exactly what I need. I’ll play with it and once I have it working as I expect, I’ll post the full config back here for future reference for others.

I appreciate you taking the time to help me with this, and for Caddy!

1 Like

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