The Error
You hit this in the browser:
NET::ERR_CERT_COMMON_NAME_INVALID
The server's security certificate does not match the website's URL.
The certificate on your server doesn't cover the domain the browser is trying to reach. The browser refuses to connect.
Root Cause
TLS certificates declare which domains they cover in two fields: the Common Name (CN) and the Subject Alternative Names (SAN) extension. Modern browsers only check SANs. Chrome dropped CN-only matching in Chrome 58 (2017), and every other major browser followed.
The usual culprits:
- Certificate issued for
example.combut you're visitingwww.example.com(or vice versa) - Certificate issued for one domain, deployed on a server hosting a different domain
- Accessing a server by IP address when the cert only lists a hostname
- Wildcard cert
*.example.comโ covers subdomains but NOT bareexample.com - Self-signed cert generated without a SAN extension
- Stale cert file from a previous domain still configured on the server
Step 1: Diagnose the Mismatch
Before touching anything, find out exactly what domains the deployed cert actually covers:
# Check which names the cert lists in its SAN field
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null \
| openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
# Also check the CN
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null \
| openssl x509 -noout -subject
A mismatched cert looks like this:
Subject: CN = old-domain.com
X509v3 Subject Alternative Name:
DNS:old-domain.com, DNS:www.old-domain.com
If your domain isn't listed under SAN, that's your problem.
Fix 1: Reissue the Certificate with the Correct Domains
The right long-term fix: get a new cert that actually covers your domain. Use Let's Encrypt with Certbot:
# Single domain + www
certbot --nginx -d example.com -d www.example.com
# Or with Apache
certbot --apache -d example.com -d www.example.com
# Standalone mode (no web server integration)
certbot certonly --standalone -d example.com -d www.example.com
Certbot automatically adds both domains to the SAN field. Reload your web server after:
sudo systemctl reload nginx
# or
sudo systemctl reload apache2
Fix 2: Right Cert, Wrong Path โ Point the Server to the Correct File
Sometimes the correct cert already exists on the server โ it's just not the file your config is loading. Check and fix the paths.
Nginx:
server {
listen 443 ssl;
server_name example.com www.example.com;
# Make sure these point to the cert covering example.com
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
}
Apache:
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
</VirtualHost>
Test the config before reloading โ never skip this step:
sudo nginx -t && sudo systemctl reload nginx
# or
sudo apachectl configtest && sudo systemctl reload apache2
Fix 3: Self-Signed Cert Missing SAN (Dev/Internal)
A plain openssl req command generates a cert with CN only โ no SAN extension. Chrome rejects these outright. You need to pass a config file that explicitly includes SAN entries:
# Create an OpenSSL config with SAN
cat > cert.cnf <<EOF
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = myapp.local
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = myapp.local
DNS.2 = localhost
IP.1 = 127.0.0.1
EOF
# Generate cert with SAN included
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout myapp.key -out myapp.crt -config cert.cnf
Then trust it locally. On macOS:
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain myapp.crt
On Ubuntu/Debian:
sudo cp myapp.crt /usr/local/share/ca-certificates/myapp.crt
sudo update-ca-certificates
Fix 4: Wildcard Cert Doesn't Cover the Root Domain
*.example.com covers www.example.com, api.example.com, and any other subdomain โ but bare example.com is not included. Reissue with both explicitly listed:
certbot certonly --nginx -d example.com -d '*.example.com' \
--preferred-challenges dns-01
Wildcard certs require the DNS-01 challenge instead of HTTP-01. During issuance, Certbot will ask you to add a _acme-challenge TXT record to your DNS zone. Add it, wait 30โ60 seconds for propagation, then continue.
Verify the Fix
After any change, confirm the cert now lists the right domains before declaring victory:
# Check SAN entries on live server
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -text | grep -A5 "Subject Alternative Name"
# Expected output:
# X509v3 Subject Alternative Name:
# DNS:example.com, DNS:www.example.com
# Quick validity check
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -dates
Cross-check with curl โ a valid cert returns a normal response header, no SSL warnings:
curl -vI https://example.com 2>&1 | grep -E "SSL|subject|issuer|expire"
No SSL certificate problem in the output? You're done.
Prevention
- Always include both bare domain and www in every cert request, even when you redirect one to the other. Takes five seconds to add
-d www.example.com; takes much longer to debug later. - Set up auto-renewal. Test it first with
certbot renew --dry-run, then rely on the systemd timer Certbot installs at/etc/systemd/system/snap.certbot.renew.timer(or add your own cron). Let's Encrypt certs expire in 90 days โ don't manage this manually. - Test certs before deploying with
openssl verify -CAfile chain.pem cert.pemor run your domain through SSL Labs (ssllabs.com/ssltest) for a full report including SAN coverage. - For internal services, use a proper internal CA โ tools like step-ca or cfssl make it straightforward. Ad-hoc self-signed certs are fine for one machine; they become a maintenance nightmare across a team.

