Issue Let's Encrypt Wildcard SSL with Certbot DNS Challenge

A Wildcard SSL certificate for *.example.com covers every subdomain (blog.example.com, shop.example.com) under one certificate — but Let's Encrypt cannot issue wildcard certificates over HTTP-01. You must use the DNS-01 challenge.

This guide shows how to obtain a free Let's Encrypt Wildcard certificate using Certbot + DNS challenge, both manually and automatically via the Cloudflare DNS API, on an Ubuntu VPS — and how to auto-renew every 60 days.

Prerequisites: A domain whose DNS you control (the web server can be elsewhere), an Ubuntu VPS with sudo access. For the automatic path, create a Cloudflare API token with Zone:DNS Edit scope.

HTTP-01 vs DNS-01 Challenge

TopicHTTP-01DNS-01
VerificationFile at .well-known/acme-challenge/TXT record in DNS
Wildcard❌ Not supported✅ Supported
Needs Web ServerYes — port 80 openNo
Auto RenewEasy — Certbot handles itNeeds DNS API
Best forSingle-host certificatesWildcards, internal services

Why Wildcard SSL Is the Right Choice for Multi-Subdomain VPS Deployments

When your server hosts multiple subdomains — shop.example.com, blog.example.com, api.example.com, admin.example.com — issuing individual certificates for each one multiplies your management overhead and renewal schedule. A single wildcard certificate eliminates that complexity. Key advantages on a VPS:

The key limitation to keep in mind: wildcards cover exactly one subdomain level. *.example.com covers a.example.com but not b.a.example.com. For sub-subdomains you need a separate wildcard such as *.a.example.com or a multi-SAN certificate.

Path 1 — Manual DNS Challenge (One-Off)

Use this if you only need it occasionally and your DNS has no API — but expect to paste a TXT record every 90 days at renewal.

1.1 Install Certbot

sudo apt update
sudo apt install -y certbot

1.2 Request a Wildcard Certificate Manually

sudo certbot certonly --manual --preferred-challenges=dns \
  --email [email protected] --agree-tos --no-eff-email \
  -d example.com -d "*.example.com"

Certbot prints a TXT record to deploy, e.g.

Please deploy a DNS TXT record under the name:
_acme-challenge.example.com.

with the following value:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Add that TXT record in your DNS panel (Cloudflare / Route 53 / DirectAdmin DNS), wait 1–2 minutes for propagation, then press Enter. Certbot verifies and issues the certificate under /etc/letsencrypt/live/example.com/, covering both the root and every subdomain.

Path 2 — Automatic via Cloudflare DNS Plugin

If your DNS is on Cloudflare, Certbot can issue and renew without any manual intervention.

2.1 Install the Cloudflare Plugin

sudo apt install -y python3-certbot-dns-cloudflare

2.2 Create a Cloudflare API Token

Go to My Profile → API Tokens → Create Token → Edit zone DNS, select the zone, and save the token.

2.3 Store the Token (Important: chmod 600)

sudo mkdir -p /etc/letsencrypt/cloudflare
sudo nano /etc/letsencrypt/cloudflare/credentials.ini

Contents:

dns_cloudflare_api_token = YOUR_CLOUDFLARE_TOKEN_HERE
sudo chmod 600 /etc/letsencrypt/cloudflare/credentials.ini

⚠️ The credentials file must be mode 600. Otherwise Certbot refuses to use it for security reasons.

2.4 Request the Wildcard Certificate

sudo certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare/credentials.ini \
  --dns-cloudflare-propagation-seconds 30 \
  --email [email protected] --agree-tos --no-eff-email \
  -d example.com -d "*.example.com"

Certbot calls Cloudflare's API to add and remove the TXT record automatically. The certificate lands in /etc/letsencrypt/live/example.com/.

Step 3 — Wire Up Nginx

In your Nginx server block:

server {
    listen 443 ssl http2;
    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;

    # recommended hardening
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    root /var/www/example;
    index index.html;
}

Test and reload:

sudo nginx -t
sudo systemctl reload nginx

Step 4 — Auto-Renew Every 60 Days

Certbot ships a systemd timer. Check it:

sudo systemctl list-timers | grep certbot

Dry-run a renewal:

sudo certbot renew --dry-run

Reload Nginx automatically after every renewal:

sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Contents:

#!/bin/bash
systemctl reload nginx
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Configuring Nginx Virtual Hosts to Share One Wildcard Certificate

Once you have the wildcard certificate, you reference the same certificate files in every virtual host. Here is an example with two subdomains in separate config files:

# /etc/nginx/sites-available/shop.conf
server {
    listen 443 ssl http2;
    server_name shop.example.com;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    root /var/www/shop;
    index index.html index.php;
}

# /etc/nginx/sites-available/blog.conf
server {
    listen 443 ssl http2;
    server_name blog.example.com;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    root /var/www/blog;
    index index.html index.php;
}

Both virtual hosts share the same certificate pair. When Certbot renews every 60 days, the renewal hook reloads Nginx automatically — you never need to touch the individual virtual host configs.

ls -la /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Troubleshooting Common Wildcard SSL Issues

The following table covers the most frequent errors encountered during DNS-01 issuance and renewal:

Problem Cause Fix
DNS propagation timeout TXT record not visible before Certbot checks Increase --dns-cloudflare-propagation-seconds 60
credentials.ini permission error File permissions are not 600 sudo chmod 600 credentials.ini
Rate limit exceeded More than 5 failed orders per domain per week Wait 7 days or use --staging when testing
Nginx not reloaded after renewal Missing or non-executable renewal hook Check chmod +x reload-nginx.sh

Always test with the staging environment first to avoid hitting rate limits during development:

sudo certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare/credentials.ini \
  --staging \
  -d example.com -d "*.example.com"

Check Expiration

sudo certbot certificates
# or
sudo openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -dates

Wildcard SSL Hardening Checklist

  1. Keep credentials.ini at permission 600
  2. Use a scoped Cloudflare API token (Zone:DNS Edit) — never the Global API Key
  3. Allow only TLSv1.2 TLSv1.3 in Nginx; disable TLS 1.0/1.1
  4. Configure Auto-Renew with a renewal hook to reload Nginx
  5. Periodically scan with AsiaGB SSL Server Test
  6. Wildcards cover one level only*.example.com covers a.example.com but not b.a.example.com

Need a VPS for Web + Wildcard SSL?

AsiaGB VPS from 500 THB/month with full root access — open ports freely for Let's Encrypt and Nginx. Choose Thailand / Singapore locations.

View VPS Plans