Wildcard SSL/TLS Certificates - Best Practices and Pitfalls
Here’s a scenario that plays out more often than it should: a team deploys a wildcard certificate to cover their 50 microservice subdomains, saves themselves a ton of cert management overhead, and feels pretty clever about it. Then one developer’s laptop gets compromised. Suddenly, that single private key issue becomes a potential exposure across every single subdomain in production.
Wildcard certificates solve real problems — but they introduce real risks too. This guide is about knowing when to reach for a wildcard and when to step back and think twice. We’ll cover the good use cases, the security considerations, and the DNS-01 challenge mechanics that make automated wildcard issuance possible.
If you’re managing many certificates and wondering which tool to use for tracking them all, our guide to choosing the right certificate manager covers what features matter most at different scales.
What Is a Wildcard Certificate?
A wildcard certificate is an SSL/TLS certificate that secures a main domain and all its first-level subdomains. It uses an asterisk (*) as a wildcard.
Example
A certificate for *.example.com secures:
- ✅
blog.example.com - ✅
shop.example.com - ✅
api.example.com - ❌
example.com(main domain - must be added separately) - ❌
admin.api.example.com(second-level subdomain)
Multi-Domain Wildcard
You can combine wildcard with SAN (Subject Alternative Names):
Common Name: *.example.com
SAN:
- *.example.com
- example.com
- *.staging.example.com
This secures:
example.comblog.example.comshop.example.comapi.staging.example.com
When to Use Wildcard Certificates?
✅ Good Use Cases
1. Development and Testing Environments
dev-feature1.staging.example.com
dev-feature2.staging.example.com
dev-feature3.staging.example.com
A wildcard certificate for *.staging.example.com covers all branch deployments.
2. SaaS Applications with Customer Subdomains
customer1.app.example.com
customer2.app.example.com
customer3.app.example.com
One certificate for *.app.example.com instead of hundreds of individual ones.
3. Microservices with Dynamic Subdomains
service-auth.internal.example.com
service-api.internal.example.com
service-db.internal.example.com
4. CDN and Load Balancers
cdn1.assets.example.com
cdn2.assets.example.com
cdn3.assets.example.com
❌ Bad Use Cases
1. Single Production Applications
If you only have www.example.com and api.example.com, use a multi-domain (SAN) certificate instead of wildcard.
2. High Security Requirements
For sensitive systems (banking, healthcare), it’s better to isolate certificates.
3. Different Subdomain Owners
If different teams manage different subdomains, sharing a certificate can be problematic.
4. When You Need EV Certificates
Extended Validation certificates are not available as wildcard.
Wildcard Certificate Security
Compromise Risk
Problem: If the private key is compromised, all subdomains are at risk.
Solution:
- Hardware Security Modules (HSM)
# Store keys in HSM
# Never export keys in plaintext
- Limited Access
# Permissions for private key
chmod 600 /etc/ssl/private/wildcard.key
chown root:root /etc/ssl/private/wildcard.key
- Key Rotation
# Renew certificate with new key every 3-6 months
# Don't use the same key/certificate pair for years
Certificate Pinning
For mobile applications using wildcard certificates:
// Android - Certificate Pinning
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("*.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.add("*.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=") // backup
.build();
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();
// iOS - Certificate Pinning
let evaluator = PinnedCertificatesTrustEvaluator(
certificates: [Certificates.wildcard],
acceptSelfSignedCertificates: false,
performDefaultValidation: true,
validateHost: true
)
let serverTrustManager = ServerTrustManager(evaluators: ["*.example.com": evaluator])
Monitoring and Auditing
# Monitor private key access (auditd)
auditctl -w /etc/ssl/private/wildcard.key -p rwxa -k wildcard_key_access
# Review logs
ausearch -k wildcard_key_access
Obtaining Wildcard Certificates
Let’s Encrypt with DNS-01 Challenge
Why DNS-01? HTTP-01 doesn’t work for wildcard because Let’s Encrypt cannot verify all possible subdomains. If you’re running Kubernetes, cert-manager handles the entire DNS-01 flow automatically — including Cloudflare and Route53 integrations — which means you get wildcard renewal without touching a cron job.
With Cloudflare
# Install certbot with Cloudflare plugin
apt-get install certbot python3-certbot-dns-cloudflare
# Credentials file
cat > /root/.cloudflare.ini <<EOF
dns_cloudflare_api_token = your-cloudflare-api-token
EOF
chmod 600 /root/.cloudflare.ini
# Obtain certificate
certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /root/.cloudflare.ini \
-d example.com \
-d *.example.com \
--email admin@example.com \
--agree-tos \
--non-interactive
# Automatic renewal
echo "0 3 * * * certbot renew --quiet" | crontab -
With Route53 (AWS)
# Install plugin
apt-get install certbot python3-certbot-dns-route53
# Credentials via AWS CLI or IAM role
aws configure
# Obtain certificate
certbot certonly \
--dns-route53 \
-d example.com \
-d *.example.com \
--email admin@example.com \
--agree-tos \
--non-interactive
With acme.sh (Universal)
# Installation
curl https://get.acme.sh | sh
# Cloudflare
export CF_Token="your-cloudflare-api-token"
acme.sh --issue --dns dns_cf -d example.com -d *.example.com
# Route53
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
acme.sh --issue --dns dns_aws -d example.com -d *.example.com
# Google Cloud DNS
export GCE_PROJECT="your-project"
export GCE_SERVICE_ACCOUNT_FILE="/path/to/key.json"
acme.sh --issue --dns dns_gcloud -d example.com -d *.example.com
Commercial CAs
Most commercial CAs offer wildcard certificates:
- DigiCert: $295-2995/year
- Sectigo: $99-299/year
- GlobalSign: $249-1299/year
- GoDaddy: $300-500/year
Choosing a commercial CA makes sense when:
- You need 24/7 support
- You need insurance (warranty)
- Client requires specific CA
- You can’t use Let’s Encrypt (e.g., no DNS API access)
Web Server Configuration
Nginx
server {
listen 443 ssl http2;
server_name *.example.com example.com;
# Wildcard certificate
ssl_certificate /etc/ssl/certs/wildcard.example.com.crt;
ssl_certificate_key /etc/ssl/private/wildcard.example.com.key;
# Modern SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/certs/chain.pem;
# Routing based on subdomain
location / {
if ($host ~ ^([^.]+)\.example\.com$) {
set $subdomain $1;
proxy_pass http://backend-$subdomain;
}
if ($host = example.com) {
proxy_pass http://backend-main;
}
}
}
Apache
<VirtualHost *:443>
ServerName example.com
ServerAlias *.example.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/wildcard.example.com.crt
SSLCertificateKeyFile /etc/ssl/private/wildcard.example.com.key
SSLCertificateChainFile /etc/ssl/certs/chain.pem
# Modern SSL configuration
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
# HSTS
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
# OCSP Stapling
SSLUseStapling on
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
# Routing
RewriteEngine On
RewriteCond %{HTTP_HOST} ^([^.]+)\.example\.com$ [NC]
RewriteRule ^(.*)$ http://backend-%1$1 [P]
</VirtualHost>
Traefik (Docker/Kubernetes)
# docker-compose.yml
version: '3.8'
services:
traefik:
image: traefik:v2.10
command:
- "--providers.docker=true"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"
- "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
environment:
- CF_API_EMAIL=admin@example.com
- CF_API_KEY=your-cloudflare-api-key
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
ports:
- "443:443"
app:
image: myapp:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`*.example.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
- "traefik.http.routers.app.tls.domains[0].main=example.com"
- "traefik.http.routers.app.tls.domains[0].sans=*.example.com"
Common Problems and Solutions
Problem 1: Certificate Doesn’t Work for Main Domain
Symptom:
✅ https://blog.example.com - works
✅ https://api.example.com - works
❌ https://example.com - invalid certificate
Solution: Wildcard *.example.com doesn’t include the main domain. You must add both:
certbot certonly --dns-cloudflare \
-d example.com \
-d *.example.com
Problem 2: Second-Level Subdomain
Symptom:
✅ https://api.example.com - works
❌ https://v1.api.example.com - invalid certificate
Solution: Wildcard only works for one level. You need:
# Option 1: Add another wildcard
certbot certonly --dns-cloudflare \
-d *.example.com \
-d *.api.example.com
# Option 2: Multi-level wildcard (rarely supported)
# Some CAs offer multi-level wildcard
Problem 3: DNS Propagation
Symptom: DNS-01 challenge fails with timeout
Solution:
# Increase timeout for DNS propagation
certbot certonly --dns-cloudflare \
--dns-cloudflare-propagation-seconds 60 \
-d example.com \
-d *.example.com
# Check DNS before attempt
dig _acme-challenge.example.com TXT
Problem 4: API Rate Limits
Symptom: Let’s Encrypt returns “too many requests”
Solution:
# Test on staging
certbot certonly --dns-cloudflare \
--staging \
-d example.com \
-d *.example.com
# After tests use production
certbot certonly --dns-cloudflare \
-d example.com \
-d *.example.com
Monitoring Wildcard Certificates
Check Script
#!/bin/bash
# check-wildcard.sh
DOMAIN="*.example.com"
CERT_FILE="/etc/ssl/certs/wildcard.example.com.crt"
# Check expiration date
EXPIRY=$(openssl x509 -in "$CERT_FILE" -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
CURRENT_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $CURRENT_EPOCH) / 86400 ))
echo "Certificate for $DOMAIN expires in $DAYS_LEFT days"
if [ $DAYS_LEFT -lt 30 ]; then
echo "WARNING: Certificate expires soon!"
# Send alert
curl -X POST https://your-webhook.com/alert \
-d "Certificate $DOMAIN expires in $DAYS_LEFT days"
fi
# Check if certificate is wildcard
SUBJECT=$(openssl x509 -in "$CERT_FILE" -noout -subject)
if [[ $SUBJECT == *"CN = *.example.com"* ]]; then
echo "✓ Valid wildcard certificate"
else
echo "✗ Not a wildcard certificate!"
fi
# Check if main domain is included
SAN=$(openssl x509 -in "$CERT_FILE" -noout -text | grep -A1 "Subject Alternative Name")
if [[ $SAN == *"DNS:example.com"* ]]; then
echo "✓ Main domain included in SAN"
else
echo "✗ Main domain NOT in certificate"
fi
Prometheus Monitoring
# prometheus-rules.yaml
groups:
- name: wildcard-certificates
rules:
- alert: WildcardCertificateExpiring
expr: (ssl_certificate_expiry_seconds{cn="*.example.com"} - time()) / 86400 < 30
for: 24h
labels:
severity: warning
annotations:
summary: "Wildcard certificate expiring soon"
description: "Certificate for *.example.com expires in {{ $value }} days"
- alert: WildcardCertificateExpired
expr: (ssl_certificate_expiry_seconds{cn="*.example.com"} - time()) < 0
labels:
severity: critical
annotations:
summary: "Wildcard certificate EXPIRED"
description: "Certificate for *.example.com has expired!"
CrtMgr Integration
CrtMgr can monitor wildcard certificates:
- Add domain with wildcard:
*.example.com - CrtMgr automatically detects wildcard
- Monitors expiration date
- Sends alerts 30/14/7/1 days before expiration
- API integration for automation
Best Practices
Use DNS-01 Challenge for wildcard certificates, add the main domain to SAN, rotate keys regularly (every 3-6 months), monitor expiration with alerts, limit access to private keys, test on staging before production, document where certificates are used, and backup certificates and keys in encrypted form.
Don’t use wildcards for single domains, share keys between environments, store keys in plaintext in repositories, ignore expiring certificate alerts, use the same key for years, give key access to everyone, forget to renew before expiration, or assume wildcards cover all subdomain levels.
Before deploying a wildcard certificate, verify that wildcard is truly needed (versus multi-domain), choose DNS-01 challenge method, configure DNS API access, test on staging environment, add main domain to SAN, configure automatic renewal, set up monitoring and alerts, restrict permissions on private keys, create encrypted key backups, document configuration, test all subdomains, and configure HSTS and OCSP stapling.
Wildcard certificates are powerful tools for exactly the right situations — dev environments with dynamic subdomains, SaaS platforms with per-customer subdomains, CDN edge nodes. Used correctly, they dramatically simplify certificate management. Used carelessly, a single compromised key becomes everyone’s problem.
The rule of thumb: use wildcards where you genuinely have many unpredictable subdomains, use SAN certificates where you have a known, fixed list. Whatever you choose, automate the DNS-01 renewal and monitor the expiration — one expired wildcard is all your subdomains going dark simultaneously.
Need a simple tool to monitor wildcard certificates? Check out CrtMgr — it automatically detects wildcards and sends alerts well before expiration. One place to see everything, regardless of how many subdomains are covered.
Wildcard certificates — use wisely, monitor carefully, sleep peacefully.