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.
- 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.
- Create a narrowly scoped API token that can edit DNS records for the zone only.
- Request:
certbot certonly --dns-cloudflare --dns-cloudflare-credentials /root/.secrets/cloudflare.ini -d example.com -d '*.example.com'. - Point Nginx at the certificate with
server_name example.com *.example.com;. - Add
.example.comtoALLOWED_HOSTSand use subdomain-awareCSRF_TRUSTED_ORIGINSand cookie domains if needed. - Test renewal with
certbot renew --dry-runand 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.
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.
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
- Serving tenants on their own domains: Custom Domains With Automatic HTTPS for Django Tenants.
- Standard single-domain setup: Configure HTTPS With Let's Encrypt for Django.
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.