Effortless Wildcard SSL: Secure Your Domain with Let's Encrypt, Nginx, Docker and Cloudflare DNS

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:

mkdir nginx-wildcard-ssl && cd nginx-wildcard-ssl

Create a docker-compose.yml file with the following content:

version: '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:

dns_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:

chmod 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:

docker 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:

IMPORTANT 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:

server {
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:

user 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:

docker-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:

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;'"

You can test renewal manually with:

docker 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!