Intro
Managing multiple subdomains within a website can be a complex task, especially when it comes to handling security certificates. Each subdomain typically requires its own unique SSL/TLS certificate to ensure a secure connection between the user’s browser and the server. As the number of subdomains grows, so does the challenge of keeping track of and renewing individual certificates, leading to increased administrative overhead and potential security vulnerabilities.
In response to these challenges, wildcard certificates have emerged as a valuable solution. Wildcard certificates provide a way to secure not just a single subdomain but all of its subdomains under a common domain with a single certificate.
Prerequisite
- A Caddy Instance.
- A domain name with
*
and@
typeA
DNS configuration pointing on your machine.
What is a Wildcard Certificate
A wildcard certificate is a type of SSL/TLS certificate that is designed to secure a domain and all its subdomains with a single certificate. The wildcard character *
is used in the domain name field to indicate that the certificate is valid for any subdomain under the specified domain.
For example, if you have a wildcard certificate for *.domain.name
it would be valid for sub1.domain.name
, sub2.domain.name
, sub3.domain.name
and so on. The asterisk acts as a placeholder for any subdomain.
Disclosure
In the event of a compromise of the root certificate’s private key, the security of all your subdomains is jeopardized, as they all share the same certificate.
How to configure Caddy
Caddy can be configured to use wildcard certificates.
Note
This guide presupposes that you are utilizing Caddy within a Docker container.
Add DNS Module
Since Let’s encrypt requires the DNS-01 challenge
to obtain wildcard certificate (and not the common HTTP-01 challenge
) , we will need to add a DNS module that allow Caddy to resolve the challenge.
A DNS Challenge asks to prove that we are in control of the domain DNS by putting a specific value in a TXT
record under that domain name. If it finds a match, the issuer can proceed to issue a certificate.
Note
With the
DNS-01 challenge
challenge, there is no requirement for ports443
and80
to be accessible from the internet.
A collection of DNS modules for Caddy is accessible through the GitHub caddy-dns. For my example I will be using caddy-dns/ionos. If your DNS provider is not in the list, you can refer to the caddy tutorial to see how to create a custom DNS module.
Referring to the previous post 04-install-caddy-plugins, you can modify your Dockerfile
as follows:
FROM caddy:builder AS builder
RUN xcaddy build \
--with github.com/caddyserver/transform-encoder \
--with github.com/abiosoft/caddy-exec \
--with github.com/caddy-dns/ionos
FROM caddy:latest
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
Now rebuild the Docker image with the command:
docker compose build
Add DNS Configuration
To configure caddy-dns/ionos, add the tls
directive in your Caddyfile
:
*.domain.name, domain.name {
tls {
dns ionos {env.IONOS_API_TOKEN}
}
}
This will configure Caddy to use DNS-01 challenge
instead of HTTP-01 challenge
and provide all the tools to edit your DNS settings from your DNS provider ionos.
Create the .env
file that will contain the generated IONOS_API_TOKEN
value from your DNS provider:
IONOS_API_TOKEN=<token-here>
Note
Generate the
IONOS_API_TOKEN
via the ionos developer control panel.
Then, add the .env
file to your docker-compose.yml
file:
version: "3.9"
services:
caddy:
# this is a custom docker image with the Caddy DNS module
image: caddy-custom:<version>
build:
context: .
dockerfile: ./Dockerfile
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./data:/data
- ./config:/config
env_file:
- .env
networks:
default:
external:
name: caddy
Create Subdomains
You can now add all the subdomains that you want directly in your Caddyfile
:
*.domain.name, domain.name {
tls {
dns ionos {env.IONOS_API_TOKEN}
}
@main host domain.name
handle @main {
respond "OK from domain.name"
}
@sub1 host sub1.domain.name
handle @sub1 {
respond "OK from sub1.domain.name"
}
}
Now, you have a subdomain sub1.domain.name
sharing the same certificate as the main domain domain.name
.
Hardening
In the previous post 02-caddy-hardening, I discussed a method for restricting subdomains to the local network. With the new configuration using wildcard certificates, there is now a simpler and more efficient method available.
Update your Caddyfile
with the following changes:
*.domain.name, domain.name {
tls {
dns ionos {env.IONOS_API_TOKEN}
}
@outside {
not remote_ip 192.168.0.0/24
}
@main host domain.name
handle @main {
respond "OK from domain.name"
}
@sub1 host sub1.domain.name
handle @sub1 {
respond "OK from sub1.domain.name"
}
# All the subdomain below will be accessible from
# local network only
handle @outside {
respond 401
}
@sub2 host sub2.domain.name
handle @sub2 {
respond "OK from sub2.domain.name"
}
}
With this configuration, the main domain domain.name
and the subdomain sub1.domain.name
will remain accessible from the internet, similar to the previous setup. However, the subdomain sub2.domain.name
, declared after the handle @outside
, will now only be available on the local network only.
Note
Any subdomain not configured before the
handle @outside
directive will result in a401 Unauthorized
error. This includes cases where the subdomain is not explicitly mentioned in the configuration, such asnonexistent.subdomain.name
for example.