Get a Wildcard Certificate for Django Subdomains

Issue a Let’s Encrypt wildcard certificate for *.example.com to serve Django on many subdomains: DNS-01 challenges with certbot DNS plugins or acme.sh, automatic renewal with provider API tokens, Nginx configuration, and Django host settings.

Problem statement

A multi-tenant Django app serving acme.example.com, globex.example.com, and new tenant subdomains every week cannot request a certificate per tenant by hand, and preview environments on pr-123.example.com hit Let's Encrypt rate limits when each gets its own. A wildcard certificate for *.example.com covers every subdomain at once. Let's Encrypt only issues wildcards through the DNS-01 challenge, which means your server needs API access to create DNS records automatically, including at every renewal.

Wildcard via DNS-01certbot requests*.example.comTXT recordcreated via DNS APILet's Encryptchecks TXTCert issuedrenews the same wayWildcards need DNS-01, so renewals depend on a working DNS API token.
  1. Use a DNS provider with an API supported by a certbot plugin (Cloudflare, Route 53, DigitalOcean, Google Cloud DNS, and others) or by acme.sh.
  2. Create a narrowly scoped API token that can edit DNS records for the zone only.
  3. Request: certbot certonly --dns-cloudflare --dns-cloudflare-credentials /root/.secrets/cloudflare.ini -d example.com -d '*.example.com'.
  4. Point Nginx at the certificate with server_name example.com *.example.com;.
  5. Add .example.com to ALLOWED_HOSTS and use subdomain-aware CSRF_TRUSTED_ORIGINS and cookie domains if needed.
  6. Test renewal with certbot renew --dry-run and add a deploy hook to reload Nginx.

Step-by-step solution

1. Install certbot with a DNS plugin

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

Other plugins: python3-certbot-dns-route53, python3-certbot-dns-digitalocean, python3-certbot-dns-google. If your provider has no certbot plugin, acme.sh supports over a hundred DNS APIs.

2. Create a scoped API token

For Cloudflare, create an API token with Zone → DNS → Edit permission for the one zone. Store it readable only by root:

sudo install -d -m 700 /root/.secrets
echo "dns_cloudflare_api_token = <token>" | sudo tee /root/.secrets/cloudflare.ini >/dev/null
sudo chmod 600 /root/.secrets/cloudflare.ini

For Route 53, prefer an instance role with permission to change records in the specific hosted zone over static keys.

certificate for example.com and *.example.comCovered?Whyexample.com✓yeslisted explicitlyacme.example.com✓yesmatches *.example.compr-42.example.com✓yesmatches *.example.comapi.eu.example.com✕nowildcards cover one level onlyRequest the apex and the wildcard together; deeper levels need their own wildcarddjango-deployment.com
Add *.eu.example.com separately if you nest subdomains.

3. Request the certificate

sudo certbot certonly \
  --dns-cloudflare --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
  --dns-cloudflare-propagation-seconds 30 \
  -d example.com -d '*.example.com' \
  --deploy-hook "systemctl reload nginx"

Quote '*.example.com' so the shell does not expand it. The wildcard does not cover the apex, so list both. --deploy-hook runs after every successful renewal.

4. Configure Nginx

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

    location / {
        proxy_pass http://unix:/run/gunicorn/gunicorn.sock;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Add a DNS wildcard record (*.example.com A <server-ip>) so every subdomain resolves to the server.

5. Django settings for subdomains

ALLOWED_HOSTS = [".example.com"]             # apex and all subdomains
CSRF_TRUSTED_ORIGINS = ["https://*.example.com", "https://example.com"]
SESSION_COOKIE_DOMAIN = None                 # per-subdomain sessions (safer for tenants)

For multi-tenant apps, keep cookies per subdomain so one tenant's session cannot be read on another tenant's subdomain. Share cookies across subdomains (".example.com") only when that is the intended behaviour, such as a single sign-in across app. and admin..

6. Verify renewal

sudo certbot renew --dry-run
systemctl list-timers | grep certbot

Renewal uses the same DNS API, so an expired or revoked token silently breaks it 60 days later. Monitor certificate expiry externally.

wildcard certificate setupDNS plugin and scoped API tokenzone DNS edit onlyApex and *.example.com requestedwildcard skips the apexWildcard DNS record to the server*.example.com AALLOWED_HOSTS .example.comper-subdomain cookies for tenantsrenew --dry-run and expiry alertstoken must keep workingThe renewal depends on the DNS token, so monitor expirydjango-deployment.com
Store the token like any other production secret.

Verification check: echo | openssl s_client -connect acme.example.com:443 -servername acme.example.com 2>/dev/null | openssl x509 -noout -ext subjectAltName lists DNS:*.example.com, DNS:example.com; a brand-new subdomain loads over HTTPS without issuing anything new; certbot renew --dry-run succeeds.

Explanation

The DNS-01 challenge proves domain control by publishing a TXT record at _acme-challenge.example.com with a value derived from the ACME order. Because control over DNS implies control over every name in the zone, Let's Encrypt allows wildcard issuance only through this challenge. Certbot's DNS plugins create and remove that record through the provider's API, so issuance and renewal run unattended. One wildcard certificate then serves any number of subdomains, which also keeps you far from per-domain rate limits.

Edge cases / notes

  • Custom tenant domains (shop.customer.com) are not covered by your wildcard; use on-demand TLS for those.
  • Cloudflare proxy in front: the edge certificate is Cloudflare's; the wildcard on your origin secures the Cloudflare-to-origin hop in Full (strict) mode.
  • Several servers need the same certificate; distribute it from one issuer or issue on each server with the same token.
  • CAA records must allow Let's Encrypt (issuewild "letsencrypt.org") if you publish CAA.

Next steps

FAQ

How do I get a wildcard certificate from Let's Encrypt?

Use the DNS-01 challenge with a certbot DNS plugin or acme.sh and your DNS provider's API.

Does *.example.com cover example.com?

No. Request both names on the same certificate.

Can I use HTTP-01 for wildcards?

No. Let's Encrypt only issues wildcard certificates through DNS-01.

Why did my wildcard renewal fail?

Usually the DNS API token expired or lost permissions. Test with certbot renew --dry-run.

Keep going