Automatic TLS Certificates when using Caddy + Cloudflare DNS

metadata

Updated

17Jul24

📖 3 mins

content

I self host a number of tools on my server at home, including this very website! Caddy is an incredibly simple and low maintenance webserver/reverse proxy and I use it to provide external access to applications.

Context

Originally, I had set up my domain name george.dev to be directly pointed at the Caddy server. As Caddy automatically configures SSL, it was absolutely fine and I needn't have changed anything to be perfectly honest.

However, I was looking to move (and consolidate) my domain names into one registrar and I ended up at Cloudflare.

After setting up the domain, DNS and Cloudflare proxy, I was unable to connect to this site or any of my other sites served through Caddy. A glance at the logs showed me this was down to Caddy no longer being able to validate the SSL cert challenges.

By default, Caddy uses TLS-ALPN challenges, but Cloudflare's proxy only permits HTTPS traffic. The fallback option is HTTP challenges, but this requires setting up Caddy with plain old HTTP support & without Cloudflare proxy, in order to gain issuance of the first certificate. Once that cert is in use and Caddy is serving over HTTPS, Cloudflare proxy can be re-introduced and certificates can be renewed going forwards.

I didn't really like this approach as it involves ordered steps and I could see a potential for it all to fall down and require redoing in the future.

DNS challenges to the rescue

GitHub - caddy-dns/cloudflare: Caddy module: dns.providers.cloudflare

In order to resolve these SSL challenge issues, we can force Caddy to use ACME DNS based challenges rather than TLS or HTTP.

This involves the use of the dns.providers.cloudflare module, which is able to update a Cloudflare DNS zone on the fly in order to provide the required records to solve the challenges.

The below GitHub repo provides a Caddy container image on GHCR with the Cloudflare DNS module and AuthP baked in GitHub - georgepstaylor/docker-custom-caddy: Caddy server with extra plugins

It's as simple as including the Cloudflare DNS TLS directive and injecting your Cloudflare API token as an environment variable into the caddy container.

https://george.dev {

  tls {
      dns cloudflare {env.CLOUDFLARE_AUTH_TOKEN}
   }

  handle {
	reverse_proxy ghost:2368
  }

    encode zstd gzip
}

Creating a Cloudflare API token

Head to your profile in the Cloudflare Admin console

cloudflare api tokens page

Create the token with the Zone.Zone permission and ideally limit it's scope to the specific zone(s) required. It's also a good idea to limit the client IP address that can be used to authenticate with your new token.

Cloudflare options

SSL:

SSL Mode

SSL in Cloudflare has a few different modes, by default it's set to flexible where only client to proxy is encrypted. With Caddy we can use Full (strict) as both the standard letsencrypt and zerossl issue from a trusted CA. This means both traffic from client -> proxy and proxy -> caddy is encrypted.

DNS Proxy:

Setting up the DNS

By enabling the Cloudflare proxy within the DNS settings for your zone, you can also take advantage of DDoS protection, WAF (Web Application Firewall) and static content caching which is all currently offered as part of their free tier. Ultimately, this increases security and speed, as well as reducing the traffic actually hitting your origin server.