Caddy Wildcard Certificates with Cloudflare DNS Challenge
Wildcard certificates have their advantages and drawbacks. In this note, we will set them up with Caddy, and use a “variation” of Docker secrets to store and securely share the Cloudflare API token with the Caddy executable.
Benefits and drawbacks
Firstly, we should talk about the drawbacks. As the Information Sheet linked from this press release of the NSA details, there are several downsides of wildcard certificates. The main risk vector doesn’t concern my use case as I only have a single server, not multiple ones. Nevertheless, we should keep the disadvantages in mind before implementing this technique.
However, there are many benefits to wildcard certificates as well. For me, the main one is to keep the protected subdomains completely unknown to the public. Whenever we request an SSL certificate for a new subdomain, we effectively broadcast that “Hey, I created my.secret.subdomain.example.com, you should check it out!” to everybody. How? With Certificate Transparency. Every issued certificate is added to a secure and publicly available list of certificates that can be queried by anyone, for example at crt.sh. Looking up any domain, we can see every certificate ever issued for any subdomains in the last ~decade (the first CT log was launched in March 2013 by Google).
Create the Cloudflare API token
Let’s start with the API token: we’ll need this token for Caddy to be able to add or modify the required record for the DNS challenge. Since our DNS is managed by Cloudflare, we should open our Cloudflare profile, select API tokens, and create a new token with the permissions Zone.Zone
and Zone.DNS
. Save the generated token since it’s only shown this one time.
Build Caddy with the Cloudflare module
Next, we’ll need to build Caddy with the Cloudflare module included. The caddy service in our current docker-compose.yml
begins like this:
We will need to replace the image: caddy:2
line with the following (replace the path as necessary):
Then, create a Dockerfile
at the above location, with the following content:
This will build Caddy with the required module for our setup.
Securely share the API key with the Caddy executable
We will need to share the Cloudflare API token with the Caddy executable in a secure manner. In most tutorials, it’s handled with Docker environment variables even though the Docker documentation clearly discourages this:
Don’t use environment variables to pass sensitive information, such as passwords, in to your containers. Use secrets instead.
Unfortunately, Docker secrets are only accessible as files under /run/secrets
, but we need the content of the file to use in our Caddyfile. There are workarounds like this to modify the container entrypoint to a script that converts secrets to environment variables, but with this method, we would need to add a new entrypoint script to our container.
Instead, we will use Caddy’s command line switch --entrypoint
to provide our secret to the Caddy executable. In our docker-compose.yml
, we define the secret, make it available to the Caddy service, then modify the container’s entrypoint to load the token from the secret file. If we define an entrypoint
for a service in our docker-compose.yml, Docker won’t use the CMD
or ENTRYPOINT
instructions in the Dockerfile
.
We save our token in the above-mentioned file (in my case ./caddy/.cloudflare_api_token
):
The file’s permissions should be 600
– only the file owner should be able to read and write it.
Lastly, we modify our Caddyfile
:
The rest of the Caddyfile follows this example pattern for wildcard certificates.
We can list the environment variables of the container with
and check whether our API token is among them – it won’t be there. In conclusion, this is a safe way to share a secret with our containers.
Thanks for reading! If you have any comments, additions, or corrections, feel free to reach me via e-mail.