CrtMgr Blog - SSL/TLS Certificate Management Insights

Expert insights on SSL/TLS certificate management, types, deployment, and best practices

Wildcard SSL/TLS Certificates - Best Practices and Pitfalls

SSL TLS Wildcard Security Certificates DNS

Wildcard certificates are a powerful tool in every administrator’s arsenal, but as Spider-Man says - “with great power comes great responsibility”. In this article, we’ll show when to use wildcard certificates, how to deploy them securely, and what pitfalls to avoid.

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.com
  • blog.example.com
  • shop.example.com
  • api.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:

  1. Hardware Security Modules (HSM)
# Store keys in HSM
# Never export keys in plaintext
  1. Limited Access
# Permissions for private key
chmod 600 /etc/ssl/private/wildcard.key
chown root:root /etc/ssl/private/wildcard.key
  1. 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.

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 [email protected] \
  --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 [email protected] \
  --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"
      - "[email protected]"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    environment:
      - [email protected]
      - 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:

  1. Add domain with wildcard: *.example.com
  2. CrtMgr automatically detects wildcard
  3. Monitors expiration date
  4. Sends alerts 30/14/7/1 days before expiration
  5. API integration for automation

Best Practices - Summary

✅ DO

  1. Use DNS-01 Challenge for wildcard certificates
  2. Add main domain to SAN
  3. Rotate keys regularly (every 3-6 months)
  4. Monitor expiration with alerts
  5. Limit access to private key
  6. Test on staging before production
  7. Document where certificate is used
  8. Backup certificates and keys (encrypted!)

❌ DON’T

  1. Don’t use wildcard for single domains
  2. Don’t share keys between environments
  3. Don’t store keys in plaintext in repo
  4. Don’t ignore expiring certificate alerts
  5. Don’t use the same key for years
  6. Don’t give key access to everyone
  7. Don’t forget to renew before expiration
  8. Don’t assume wildcard covers all subdomain levels

Deployment Checklist

Before deploying a wildcard certificate:

  • Verify that wildcard is needed (vs multi-domain)
  • Choose challenge method (DNS-01 for wildcard)
  • 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 key
  • Create backup of key (encrypted)
  • Document configuration
  • Test all subdomains
  • Configure HSTS and OCSP stapling

Summary

Wildcard certificates are a powerful tool that can greatly simplify SSL/TLS management for multiple subdomains. Key points:

  • Use wisely - not for everything, but where it makes sense
  • Security first - wildcard increases compromise risk
  • Automate - DNS-01 challenge with automatic renewal
  • Monitor - one expired wildcard = all subdomains down
  • Document - know where certificate is used

Need a simple tool to monitor wildcard certificates? Check out CrtMgr - automatically detects and monitors wildcard certificates with alerts before expiration.


Wildcard certificates - use wisely, monitor carefully, sleep peacefully.

Related Articles