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:
Now rebuild the Docker image with the command:
Add DNS Configuration
To configure caddy-dns/ionos, add the tls
directive in your Caddyfile
:
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:
Note
Generate the
IONOS_API_TOKEN
via the ionos developer control panel.
Then, add the .env
file to your docker-compose.yml
file:
Create Subdomains
You can now add all the subdomains that you want directly in your Caddyfile
:
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:
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.