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 chain
  • privkey.pem — the private key
  • chain.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

FeatureWildcard (*.example.com)SAN (Multi-Domain)Individual Certs
Covers all subdomainsYes (first-level only)Only listed domainsOne domain per cert
Root domain coverageMust be added explicitlyYes, if listedYes
Multi-level subdomainsNo (*.*.example.com unsupported)Yes, if listedYes
Number of certificates111 per domain
DNS challenge requiredYesNo (HTTP-01 works)No (HTTP-01 works)
Renewal complexityNeeds DNS plugin or manual stepCan use HTTP-01Simple with HTTP-01
Best forMany subdomains, dynamic subdomainsFixed set of different domainsSingle-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.com does NOT match example.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 60 if 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 --staging for testing.
  • Multi-level subdomains: *.example.com does NOT cover dev.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/ with 600 permissions 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.com and example.com in the same certificate
  • Store DNS plugin credentials securely with chmod 600
  • Configure Nginx with ssl_certificate pointing 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 --staging flag during testing to avoid rate limits