Solving Chrome’s ERR_CERT_COMMON_NAME_INVALID for Wildcard *.localhost with Nginx and Self-Signed SSL


2 views

When working with wildcard subdomains under *.localhost, Chrome's strict SSL validation can be particularly problematic. Modern browsers have deprecated support for Subject Common Name (CN) matching in favor of Subject Alternative Name (SAN) extensions.

The error net::ERR_CERT_COMMON_NAME_INVALID occurs because:

  • Chrome no longer trusts CN-based wildcards for localhost
  • The certificate lacks proper SAN extensions
  • Modern browsers require explicit domain specification

Generate a new certificate with SAN support using this OpenSSL config file (localhost.cnf):

[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
x509_extensions = v3_req

[dn]
C = AU
ST = Western Australia
L = Perth
O = Zephon
CN = *.localhost

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
DNS.2 = *.localhost

Then generate the certificate:

openssl req -x509 -newkey rsa:2048 \
-keyout localhost.key -out localhost.crt \
-days 3650 -nodes -config localhost.cnf

Modify your Nginx config to explicitly handle wildcard subdomains:

server {
    listen       80;
    listen       443 ssl;
    
    server_name  ~^(?.+).localhost$ localhost;

    ssl_certificate /etc/nginx/ssl/localhost.crt;
    ssl_certificate_key /etc/nginx/ssl/localhost.key;

    # SSL optimizations
    ssl_session_timeout  1d;
    ssl_session_cache    shared:SSL:10m;
    ssl_protocols        TLSv1.2 TLSv1.3;
    ssl_ciphers          HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    location / {
        proxy_pass          http://localhost:3000;
        proxy_http_version  1.1;
        proxy_set_header    Host             $host;
        proxy_set_header    Upgrade          $http_upgrade;
        proxy_set_header    Connection       "upgrade";
        proxy_set_header    X-Forwarded-Proto $scheme;
        proxy_set_header    X-Real-IP        $remote_addr;
        proxy_set_header    X-Forwarded-For  $proxy_add_x_forwarded_for;
    }
}

After generating the new certificate:

  1. Import the CRT file into Chrome's certificate store
  2. Restart Chrome completely (chrome://restart)
  3. Verify the certificate appears in chrome://settings/certificates
  4. For testing, you might need to:
# Temporarily disable Chrome's security
google-chrome --ignore-certificate-errors --ignore-urlfetcher-cert-requests

For production-like local development, consider:

1. Using .test instead of .localhost (IANA reserved TLD)

2. Setting up a local CA with mkcert:

brew install mkcert  # For macOS
mkcert -install
mkcert "*.example.test" "example.test" "localhost" "127.0.0.1"

3. Using a DNS service like nip.io or sslip.io


When setting up wildcard subdomains *.localhost with Nginx reverse proxy, Chrome throws ERR_CERT_COMMON_NAME_INVALID even after importing the self-signed certificate. This occurs because modern browsers (Chrome 58+) enforce stricter SSL certificate validation through CA/Browser Forum's deprecation of Common Name matching.

Your current OpenSSL command doesn't include Subject Alternative Names (SANs), which are now mandatory. Here's the corrected version:

openssl req -x509 -sha256 -newkey rsa:2048 \
-keyout localhost.key -out localhost.crt \
-days 3650 -nodes -subj "/CN=*.localhost" \
-extensions san -config <(echo \
"[req]"; \
"distinguished_name=req"; \
"[san]"; \
"subjectAltName=DNS:localhost,DNS:*.localhost")

Your current Nginx configuration needs three critical adjustments:

server {
    listen       80;
    listen       443 ssl;
    server_name  localhost *.localhost;  # Added wildcard here
    
    ssl_certificate /etc/nginx/ssl/localhost.crt;
    ssl_certificate_key /etc/nginx/ssl/localhost.key;
    
    # Modern SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
    ssl_prefer_server_ciphers on;
    
    location / {
        proxy_pass          http://localhost:3000;
        proxy_set_header    Host $host;
        # ... rest of your proxy settings
    }
}

For local development, you can either:

  1. Create a proper CA-signed certificate using mkcert:
    mkcert "*.localhost" localhost 127.0.0.1 ::1
  2. Or enable Chrome flags temporarily:
    chrome://flags/#allow-insecure-localhost

Ensure your DNSmasq config includes:

address=/localhost/127.0.0.1
address=/.localhost/127.0.0.1

Verify resolution works with:

dig anytest.localhost @127.0.0.1

After implementing all changes:

curl -vIk https://test.localhost
openssl s_client -connect test.localhost:443 -servername test.localhost | openssl x509 -noout -text