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

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:

  1. A registered domain managed via Cloudflare (e.g., example.com)
  2. A Cloudflare API token with DNS edit permissions (see Step 2)
  3. 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:

bash
Copy code
mkdir nginx-wildcard-ssl && cd nginx-wildcard-ssl

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

yaml
Copy code
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 ProfileAPI 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:

ini
Copy code
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:

bash
Copy code
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:

bash
Copy code
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:

shell
Copy code
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:

nginx
Copy code
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:

nginx
Copy code
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:

bash
Copy code
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:

yaml
Copy code
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:

bash
Copy code
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!

Table of Contents