SSL, TLS, security headers
Replace self-signed SSL certificate with a trusted CA cert
Browsers reject self-signed certs with NET::ERR_CERT_AUTHORITY_INVALID because no public CA chains them. Replace with Let's Encrypt, ZeroSSL, or your cloud provider's managed cert.
What's happening
A self-signed certificate is one whose Issuer field equals its Subject field — there is no trust anchor in the public root store. The TLS handshake completes the cryptographic exchange, but the client's path-validation step fails because no certificate in the presented chain matches a trusted root.
Chrome surfaces this as NET::ERR_CERT_AUTHORITY_INVALID with the message 'Your connection is not private'. Firefox reports SEC_ERROR_UNKNOWN_ISSUER. Safari blocks the connection and offers no override on iOS at all. Curl returns exit code 60 with the message 'self signed certificate'.
Self-signed certificates are common in development environments and on internal load balancers, but they should never reach the public internet on a production hostname. The fix is to provision a certificate from a public CA — Let's Encrypt is free and automated and is the right default for almost every public service.
Why it matters
Public users cannot reach the site at all without manually clicking through a security warning, which approximately zero non-technical visitors will do. The deliverability hit on a public domain is total.
Search crawlers treat the hostname as untrusted and refuse to index the content. Google Search Console will surface the URL with a security issue and stop refreshing snippets and rankings until trust is restored.
Self-signed certificates are explicitly prohibited under PCI-DSS for systems handling cardholder data, and most SOC2 auditors flag them as control gaps even on internal systems if there is no documented trust-distribution process.
Common causes
- The server was provisioned with a default snake-oil certificate from the OS package and never replaced.
- Someone ran
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodesfor testing and forgot to swap it out. - The application stack ships a built-in self-signed certificate (default Tomcat, default Jenkins, default Elasticsearch) and the deployment skipped the production certificate step.
- An internal CA was used but its root was never added to the public trust store, which is by definition impossible.
- A reverse proxy is terminating TLS with a self-signed certificate intended only for the loopback connection to the upstream.
Detect this on your site
Run a quick scan with the SSL Checker. The tool surfaces this exact issue with the records and context needed to apply the fix below.
Open SSL CheckerHow to fix it
- 1
Confirm the certificate is self-signed
Run
openssl s_client -servername example.com -connect example.com:443 /dev/null | openssl x509 -noout -issuer -subject. If Issuer and Subject are identical, the certificate is self-signed. The SSL Checker at /ssl will flag this with the same diagnosis and show the full chain. - 2
Pick a public CA and method
For most servers use Let's Encrypt with certbot. For AWS-fronted services use Certificate Manager (ACM). For Cloudflare-fronted sites enable Universal SSL. For internal services that must remain internal, distribute your private root via MDM and document the chain in your runbook.
- 3
Provision via certbot
Install certbot from the OS package manager and run
sudo certbot --nginx -d example.com -d www.example.com. Certbot edits the nginx config in place, requests a certificate via HTTP-01 or DNS-01 challenge, and configures auto-renewal via systemd timer. For Apache use--apache; for standalone use--standalonewith port 80 free. - 4
Update the server config to point at the new files
If you used certbot with a plugin, this is automatic. Otherwise edit the listener: nginx wants
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;andssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;. Always use fullchain.pem, never cert.pem alone — that ships only the leaf and triggers chain-incomplete errors. - 5
Reload and verify
Run
sudo nginx -t && sudo systemctl reload nginx. Then re-run the openssl command from step 1; Issuer should now read 'Let's Encrypt' or your chosen CA. Visit the site in a clean browser window and confirm the lock icon appears with no warning. - 6
Remove the self-signed certificate from the host
Delete the old self-signed key and certificate so a future config rollback cannot accidentally restore it. Document the change in your infrastructure repo and add the renewal hook to monitoring.
Example
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
}Nginx listener using a Let's Encrypt fullchain instead of a self-signed cert
Frequently asked
Because you passed -k or --insecure which disables certificate verification entirely. Curl without that flag will fail with the same exit code 60 that browsers reject on. Disabling verification is fine for a local probe but is never a fix.
On a single dev machine yes — but you cannot distribute that decision to every visitor. For internal-only services run a private CA distribute the root via MDM (Jamf Intune or similar) and rotate it on a schedule. For public services never go down this path.
Related fixes
SSL, TLS, security headers
Fix incomplete SSL certificate chain and intermediate errors
SSL, TLS, security headers
Fix SSL certificate hostname mismatch errors in browsers
SSL, TLS, security headers
Fix expired SSL certificate and restore HTTPS access
SSL, TLS, security headers
Force HTTP-to-HTTPS redirects on every hostname you serve