Securing web applications with HTTPS is a must, and Let’s Encrypt makes it easy by offering free SSL certificates. But what if you want a wildcard certificate to cover all subdomains under a domain? Fortunately, Let’s Encrypt supports wildcard certificates via the DNS-01 challenge, which requires updating DNS TXT records.
This guide is specific to using Cloudflare as your DNS provider, using their API to automate DNS updates during certificate issuance and renewal. Let’s walk through the process step by step.
Prerequisites
Before we dive in, make sure you have:
- A registered domain managed via Cloudflare (e.g.,
example.com
) - A Cloudflare API token with DNS edit permissions (see Step 2)
- Docker and Docker Compose installed
If you use other DNS providers like DigitalOcean or AWS Route 53, you’ll need different DNS plugins and API credentials. This guide is tailored specifically for Cloudflare.
Step 1: Set Up the Docker Environment
Create a directory for your setup:
bashCopy codemkdir nginx-wildcard-ssl && cd nginx-wildcard-ssl
Create a docker-compose.yml file with the following content:
yamlCopy codeversion: '3' services: nginx: image: nginx:latest container_name: nginx restart: unless-stopped ports: - "80:80" - "443:443" volumes: ## Config - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/sites-available:/etc/nginx/sites-enabled:ro ## SSL - /etc/ssl:/etc/ssl - /data/containers/nginx/ssl/dhparam.pem:/etc/ssl/dhparam.pem:ro - /data/containers/certbot/conf:/etc/letsencrypt:ro ## Logs (optional) #- /data/containers/nginx/logs:/var/log/nginx:rw command: /bin/sh -c "while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g 'daemon off;'" networks: - web - internal certbot: container_name: certbot image: certbot/dns-cloudflare restart: unless-stopped volumes: - /data/containers/certbot/conf:/etc/letsencrypt:rw - /data/containers/certbot/www:/var/www/certbot:rw entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/.secrets/cloudflare.ini; sleep 48h & wait $${!}; nginx -s reload; done;'" networks: - internal networks: web: external: true name: nginx internal: driver: bridge
Step 2: Prepare Your Cloudflare API Token
To allow Certbot to update DNS TXT records automatically for the DNS-01 challenge, you need a Cloudflare API token with DNS edit permissions.
How to create the API token:
- Log into your Cloudflare dashboard: https://dash.cloudflare.com/.
- Click your user icon → My Profile → API Tokens.
- Click Create Token.
- Use the Edit zone DNS template or create a custom token with:
- Permissions: Zone → DNS → Edit
- Zone Resources: Restrict to your domain(s) for security.
- Name your token (e.g.,
Certbot DNS Token
). - Click Continue to summary and then Create Token.
- Copy the token immediately — you won't be able to see it again.
Save the token securely
Create a file /data/containers/certbot/conf/.secrets/cloudflare.ini
with:
iniCopy codedns_cloudflare_api_token = your_cloudflare_api_token_here
Important: This file contains sensitive credentials!
So it's recommended to restrict permissions for the file. Therefore secure the credentials file with:
bashCopy codechmod 600 /data/containers/certbot/conf/.secrets/cloudflare.ini
This command sets file permissions so only the owner can read and write the file. It prevents other users on the system from reading your API token, enhancing security.
Step 3: Request Your Wildcard Certificate
Request your wildcard certificate by running:
bashCopy codedocker run --rm \ -v /data/containers/certbot/conf:/etc/letsencrypt \ -v /data/containers/certbot/www:/var/www/certbot \ certbot/dns-cloudflare certonly \ --dns-cloudflare \ --dns-cloudflare-credentials /etc/letsencrypt/.secrets/cloudflare.ini \ --email your-email@example.com \ --agree-tos \ --no-eff-email \ -d example.com \ -d "*.example.com"
What this command does:
- Runs the Certbot Docker container with the Cloudflare DNS plugin.
- Mounts your configuration and webroot folders inside the container.
- Uses the Cloudflare API token to create temporary DNS TXT records required by Let’s Encrypt to prove domain ownership.
- Requests certificates for both your apex domain (example.com) and all subdomains (*.example.com).
- Stores the certificates in /etc/letsencrypt/live/example.com/ (inside the container, mapped to your host folder).
- Agrees to the Let's Encrypt terms of service.
- Uses your email for urgent notices (expiry reminders).
If successful, you’ll see something like:
shellCopy codeIMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/example.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/example.com/privkey.pem - Your certificate will expire on 2025-09-15. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Step 4: Configure Nginx to Use the Wildcard Certificate
Create a virtual host config, for example ./nginx/sites-available/example.conf
:
nginxCopy codeserver { listen 443 ssl; server_name example.com *.example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; ssl_dhparam /etc/ssl/dhparam.pem; location / { proxy_pass http://your_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 80; server_name example.com *.example.com; return 301 https://$host$request_uri; }
You’ll also need a basic nginx.conf
to include your sites:
nginxCopy codeuser nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; # Logging access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; # Performance sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; # Gzip Compression gzip on; gzip_disable "msie6"; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_buffers 16 8k; gzip_min_length 1024; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; # Security Headers (can be overridden in virtual hosts) add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff"; # SSL Defaults (override per-site) ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; # Include Virtual Hosts include /etc/nginx/sites-enabled/*.conf; }
Restart the stack or reload Nginx to apply changes:
bashCopy codedocker-compose up -d
Step 5: Automate Renewal and Nginx Reload
The Certbot container is configured to:
- Automatically attempt certificate renewal every 48 hours.
- After successful renewal, it triggers an Nginx reload to apply the updated certificates without downtime.
This is handled by the command in docker-compose.yml
:
yamlCopy codeentrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/.secrets/cloudflare.ini; sleep 48h & wait $${!}; nginx -s reload; done;'"
You can test renewal manually with:
bashCopy codedocker run --rm \ -v /data/containers/certbot/conf:/etc/letsencrypt \ certbot/certbot renew --dry-run
Summary
- This guide is tailored for domains managed on Cloudflare.
- You set up Docker containers running Nginx and Certbot with the Cloudflare DNS plugin.
- Created a Cloudflare API token to allow DNS automation.
- Requested and installed a wildcard SSL certificate covering your domain and all subdomains.
- Nginx uses the wildcard certificate for HTTPS.
- Automatic certificate renewal and zero-downtime Nginx reload are configured.
If you use another DNS provider, look for the appropriate Certbot DNS plugin and adjust the API credentials accordingly.
Feel free to ask if you want me to help with other providers or configurations!