Managing individual SSL certificates for every subdomain gets tedious fast. A wildcard SSL certificate from Let’s Encrypt covers all subdomains under a single domain — *.example.com — with one certificate. This guide walks through obtaining a free wildcard certificate using Certbot with the DNS-01 challenge and configuring Nginx to serve HTTPS across all your subdomains.
Prerequisites
- A Linux server (Ubuntu 22.04/24.04 or Debian 12) with root or sudo access
- Nginx installed and running
- A registered domain name with DNS access (ability to create TXT records or API access)
- Port 443 open in your firewall
- Basic familiarity with terminal commands and Nginx configuration
Understanding Wildcard Certificates
A wildcard certificate secures all first-level subdomains of a domain. A certificate for *.example.com covers www.example.com, api.example.com, staging.example.com, and any other subdomain — but not the bare domain example.com itself, and not multi-level subdomains like dev.api.example.com.
Let’s Encrypt requires the DNS-01 challenge for wildcard certificates. Unlike the HTTP-01 challenge (which drops a file on your web server), DNS-01 requires you to create a TXT record at _acme-challenge.example.com to prove domain ownership. This makes sense: a wildcard covers the entire domain, so you need to prove control over the domain’s DNS, not just a single server.
Installing Certbot and DNS Plugins
Install Certbot and the DNS plugin matching your DNS provider. For Cloudflare:
sudo apt update
sudo apt install certbot python3-certbot-nginx python3-certbot-dns-cloudflare
For other providers, replace the plugin package:
# AWS Route 53
sudo apt install python3-certbot-dns-route53
# DigitalOcean
sudo apt install python3-certbot-dns-digitalocean
# Google Cloud DNS
sudo apt install python3-certbot-dns-google
If the packaged version is outdated, install via pip:
sudo pip3 install certbot certbot-nginx certbot-dns-cloudflare
Next, create the credentials file for your DNS provider. For Cloudflare, create /etc/letsencrypt/cloudflare.ini:
# Cloudflare API token (recommended over Global API key)
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
Lock down permissions — this file contains your API credentials:
sudo chmod 600 /etc/letsencrypt/cloudflare.ini
The Cloudflare API token needs Zone:DNS:Edit permission scoped to your domain. Create one in Cloudflare Dashboard → My Profile → API Tokens.
Requesting the Wildcard Certificate
Request a certificate that covers both the wildcard and the bare domain:
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d "*.example.com" \
-d "example.com" \
--preferred-challenges dns-01 \
--agree-tos \
-m admin@example.com
Certbot creates a DNS TXT record, waits for propagation, validates it, and then cleans it up. The certificate files land in /etc/letsencrypt/live/example.com/:
fullchain.pem— the certificate plus intermediate chainprivkey.pem— the private keychain.pem— the intermediate certificate only
For manual DNS (no plugin), use --manual --preferred-challenges dns-01. Certbot will display the TXT record value for you to add manually. This works for one-off requests but cannot auto-renew.
Configuring Nginx
Create or update your Nginx server block to use the wildcard certificate. This configuration handles HTTPS for any subdomain and redirects HTTP to HTTPS:
# Redirect all HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com *.example.com;
return 301 https://$host$request_uri;
}
# HTTPS server block for all subdomains
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com *.example.com;
# Let's Encrypt wildcard certificate
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# SSL hardening
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
# HSTS (optional, enable once confirmed working)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
root /var/www/example.com/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Test and reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
For subdomain-specific routing, use separate server blocks that share the same certificate paths but have different server_name and root / proxy_pass directives.
Wildcard vs SAN vs Individual Certificates
| Feature | Wildcard (*.example.com) | SAN (Multi-Domain) | Individual Certs |
|---|---|---|---|
| Covers all subdomains | Yes (first-level only) | Only listed domains | One domain per cert |
| Root domain coverage | Must be added explicitly | Yes, if listed | Yes |
| Multi-level subdomains | No (*.*.example.com unsupported) | Yes, if listed | Yes |
| Number of certificates | 1 | 1 | 1 per domain |
| DNS challenge required | Yes | No (HTTP-01 works) | No (HTTP-01 works) |
| Renewal complexity | Needs DNS plugin or manual step | Can use HTTP-01 | Simple with HTTP-01 |
| Best for | Many subdomains, dynamic subdomains | Fixed set of different domains | Single-domain servers |
Automatic Renewal
Certbot sets up a systemd timer automatically on most distributions. Verify it’s active:
sudo systemctl status certbot.timer
If it shows active (waiting), renewal is scheduled. You can also check:
sudo certbot renew --dry-run
Add a post-renewal hook to reload Nginx after each renewal. Create /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh:
#!/bin/bash
systemctl reload nginx
Make it executable:
sudo chmod +x /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh
If you prefer cron over systemd, add to root’s crontab:
# Renew certificates twice daily (Certbot skips if not due)
0 3,15 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx"
Real-World Scenario
You’re deploying a microservices platform with api.example.com for the backend, app.example.com for the frontend, staging.example.com for testing, and docs.example.com for documentation. Without a wildcard certificate, you’d need to run Certbot for each subdomain, manage four separate certificates, and add new Certbot commands every time a new service launches. With a wildcard certificate, you issue one certbot certonly command, point every Nginx server block at the same certificate files, and new subdomains work immediately — no certificate changes needed. When the team spins up monitoring.example.com next month, it just works.
Gotchas and Edge Cases
- Root domain not covered:
*.example.comdoes NOT matchexample.com. Always include both-d "*.example.com" -d "example.com"in your Certbot command. - DNS propagation delays: Some DNS providers take minutes to propagate TXT records. Certbot defaults to 10 seconds wait. Increase it with
--dns-cloudflare-propagation-seconds 60if validation fails. - Rate limits: Let’s Encrypt allows 50 certificates per registered domain per week and 5 duplicate certificates per week. Wildcard counts as one certificate, so you’re unlikely to hit these in normal use. Use
--stagingfor testing. - Multi-level subdomains:
*.example.comdoes NOT coverdev.api.example.com. You need a separate certificate or a SAN certificate for nested subdomains. - Certificate transparency logs: All Let’s Encrypt certificates are logged publicly. Your subdomain names will be visible in CT logs. This is unavoidable with any publicly trusted certificate.
- Plugin credentials security: Your DNS API credentials grant the ability to modify DNS records. Store them in
/etc/letsencrypt/with600permissions and consider scoping API tokens to only the required domain and permissions.
Summary
- Let’s Encrypt issues free wildcard certificates covering
*.yourdomain.com, valid for 90 days - Wildcard certificates require DNS-01 challenge — use a Certbot DNS plugin for your provider
- Always request both
*.example.comandexample.comin the same certificate - Store DNS plugin credentials securely with
chmod 600 - Configure Nginx with
ssl_certificatepointing to the Let’s Encrypt fullchain - Harden SSL with TLSv1.2+, OCSP stapling, and HSTS headers
- Set up automatic renewal via systemd timer or cron with a post-hook to reload Nginx
- Use
--stagingflag during testing to avoid rate limits