Securing your web services with SSL/TLS certificates is crucial, especially when dealing with public servers. Using Caddy’s built-in HTTPS functionality is easy, but situations involving firewalls, CGNAT, or a lack of access to port 80/443 benefit from Cloudflare’s DNS challenge for seamless certificate automation. This tutorial outlines how to build a custom Caddy Docker image that integrates Cloudflare’s DNS module using xcaddy
to streamline this process.
Why Opt for Cloudflare DNS Challenge?
Caddy’s HTTP and TLS challenges work well for most, but the DNS challenge shines when:
- Your server is behind a firewall or CGNAT.
- You want to avoid exposing ports 80 and 443 to the public.
- Your setup includes a load balancer or other restrictive networking configurations.
This method authenticates your domain ownership via the Cloudflare DNS API, allowing Caddy to fetch certificates without the need for open HTTP/HTTPS ports. See How the DNS Challenge Works for more info.
Building a Custom Caddy Image
The Dockerfile follows a multi-stage build:
- Builder Stage: We use
xcaddy
to compile Caddy with the Cloudflare DNS provider plugin. - Final Stage: The resulting binary is copied to a fresh Caddy image, creating a production-ready and lean container.
How to Set Up and Run
Step 1: Create a Custom Docker Network (Optional)
Creating a custom Docker network keeps services isolated and minimizes the need to expose ports on your VPS:
docker network create caddynetwork
If you choose a different network name, ensure the compose.yml
file reflects it.
Step 2: Create your compose.yml
file
Below is the compose.yml
configuration, which builds a custom Caddy Docker image and sets up networking for enhanced security:
services:
caddy:
build:
context: .
dockerfile: Dockerfile
container_name: caddy
environment:
- CLOUDFLARE_API_TOKEN=<ENTER YOUR TOKEN HERE>
- CADDY_ACME_EMAIL=<ENTER YOUR EMAIL HERE>
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./data:/data
- ./config:/config
restart: unless-stopped
networks:
- caddynetwork
networks:
caddynetwork:
external: true
Step 3: Configure compose.yml
Rename _compose.yml
to compose.yml
and set your environment variables:
environment:
- CLOUDFLARE_API_TOKEN=<YOUR_TOKEN>
- CADDY_ACME_EMAIL=<YOUR_EMAIL>
- CLOUDFLARE_API_TOKEN: Ensure this token has permissions for DNS zone edits on Cloudflare.
- CADDY_ACME_EMAIL: This email will be used by ACME for certificate registration.
Step 4: Build and Run
Use Docker Compose to build and start the container in detached mode:
docker compose up -d --build
Step 5: Edit Your Caddyfile
Adjust your Caddyfile
with the necessary domain and service details. Here’s a sample:
example.com {
reverse_proxy localhost:8080
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
}
- Replace
example.com
with your actual domain. - Set
localhost
to the name or IP of your backend service. - Adjust
8080
to the port where your application runs.
With this setup, Caddy will use Cloudflare’s DNS challenge to obtain certificates, keeping your services secure without exposing common HTTP/HTTPS ports.
How the DNS Challenge Works
Typically, to issue an SSL/TLS certificate, Caddy (or any Certificate Authority, CA) needs to verify that you actually own the domain in question. Normally, this is done by:
- HTTP Challenge: Verifying domain ownership by placing a file on an accessible web server running on port 80.
- TLS-ALPN Challenge: Verifying via a specific response from a server running on port 443.
However, both these methods require public access to specific ports on the server (80 or 443), which may not be feasible if:
- Your server is behind a firewall or behind Carrier-Grade NAT (CGNAT) on a network that doesn’t expose external ports.
- You don’t want to expose ports 80 and 443 publicly for security or regulatory reasons.
- You’re using a load balancer or reverse proxy setup where ports may not map directly to a specific server.
Why the Cloudflare DNS Challenge Solves This
The DNS challenge verifies domain ownership by checking for specific DNS records instead of requiring public access to your server. Here’s how it works:
- Caddy uses Cloudflare’s API to add a special TXT record to your domain’s DNS settings (hosted on Cloudflare).
- The CA (e.g., Let’s Encrypt) queries Cloudflare’s DNS servers for the TXT record to verify domain ownership.
- Once verified, the CA issues a certificate to Caddy for your domain, which it saves and manages.
Because the DNS challenge doesn’t involve your server’s IP address or port accessibility, it allows Caddy to obtain certificates even when direct access to your server is restricted.
Why You Need Cloudflare API Permissions
The DNS challenge requires dynamically creating and removing DNS records on your domain during each certificate renewal. By providing Caddy with Cloudflare’s API token, you enable it to manage the necessary DNS records for verification, fully automating the certificate issuance and renewal process without exposing any ports.
In summary, the Cloudflare DNS challenge is essential when public port access is unavailable or undesirable. It’s a robust way to automate certificate management, especially in complex network environments, by verifying domain ownership through DNS rather than HTTP or TLS ports.