Guide
certificate verify failed: self-signed
Certificate verify failed self signed means the server presented an untrusted leaf or private CA. Confirm live TLS, install the correct chain, or renew if notAfter is past.
Updated May 2, 2026 828 words
certificate verify failed: self-signed is what OpenSSL and Python report when the peer certificate is not signed by a public CA your trust store knows—often a dev cert, a mis-issued leaf, or an expired private intermediate labeled like a self-signed root.
Operators see this string in CI logs, observability agents, and language HTTP clients long before marketing notices a browser warning. The failure is PKIX trust, not necessarily expiry, though an expired self-signed leaf still fails verification and may surface with the same wording on some stacks.
Treat the message as “untrusted issuer,” then prove what certificate the listener actually sends. Match hostname, notAfter, and issuer DN against what you intended to deploy before you add NODE_TLS_REJECT_UNAUTHORIZED=0 or equivalent bypasses.
First response
Paste the full TLS error, the URL or host:port, and whether the endpoint is public internet or an internal mesh. From a host that mirrors production clients, run OpenSSL against the same SNI name the app uses—not only the DNS CNAME you manage in the dashboard.
For Python or Requests failures, capture requests.get(url, verify=True) with logging enabled or reproduce with curl -v. For container pulls, note registry hostname and whether a corporate proxy terminates TLS. For databases and queues, record the exact JDBC or AMQP URL.
Document whether the cert viewer shows a corporate root, Kubernetes cluster CA, or a literal CN=localhost issuer. That distinction drives whether you import a CA bundle, renew platform certs, or fix a listener still serving a bootstrap secret.
If the error appears only in CI, diff the runner image CA bundle against production laptops. GitHub-hosted runners update roots on a schedule; self-hosted runners on frozen AMIs miss new public intermediates and may misreport trust failures alongside legitimate self-signed noise.
For webhook receivers (PagerDuty, Slack outgoing), capture the TLS probe vendor uses when they validate your callback URL—those synthetic clients use default trust stores without your corporate imports.
What this error means
During the TLS handshake the server sends an end-entity certificate (and usually intermediates). The client walks signatures until it reaches a trust anchor in its store. If the chain stops at a certificate the client does not trust, verification fails; many libraries summarize that as self signed certificate even when the leaf was signed by an internal CA you forgot to ship.
Expiry is a separate PKIX failure: an untrusted-but-valid-date cert and a trusted-but-expired cert produce different symptoms, though both block HTTPS. Always read notAfter on the leaf you receive after SNI.
Hostname verification runs only after basic chain validation. A publicly trusted cert on the wrong name can fail with name errors instead; do not assume “self-signed” means wrong CN until you read SANs.
If your incident is only expiry on an otherwise public chain, see fix an expired SSL certificate. If the issuer is wrong but trusted elsewhere, compare with ERR_CERT_AUTHORITY_INVALID.
OpenSSL prints self signed certificate for depth-zero failures and unable to get local issuer certificate when an intermediate is missing. Operations tickets often conflate the two; use openssl verify -untrusted with the PEM you deploy to reproduce client behavior before choosing import-versus-renew.
Language bindings stringify errors differently: Node may say SELF_SIGNED_CERT_IN_CHAIN, Python certificate verify failed, .NET The remote certificate is invalid. Normalize on wire captures so database, queue, and app teams work one server fix.
Common causes
A staging cluster CA mounted in production ingress still signs the leaf customers hit. Developers embed mkcert or OpenSSL-generated files on a server whose DNS names moved to production hostnames without reissuing.
Corporate TLS inspection replaces certificates with an enterprise root that CI agents never received. Mobile or desktop apps bundle an outdated CA list while the server already rotated to a new intermediate.
Kubernetes secrets update on disk but the ingress controller or sidecar keeps an old cert in memory until reload. Load balancers present a default certificate when SNI does not match any attached listener cert—often a self-signed placeholder.
ACME renewal succeeded on the origin, but the CDN or API gateway still serves the previous custom cert tied to an old upload slot. etcd and other control-plane components reuse long-lived peer certs that operators mistake for “client config problems” rather than listener material.
Mobile apps that ship a hardcoded pin list without a rotation plan can mislabel trust failures as “self signed” in crash analytics. Desktop backup agents and legacy monitoring probes use OpenSSL-based verify steps that fail early when an intermediate is missing, even though Chrome would have built a newer cross-sign chain.
Confirm from the outside
OpenSSL (public HTTPS)
HOST=api.example.com
echo | openssl s_client -connect "$HOST:443" -servername "$HOST" -showcerts 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
Past notAfter= means renew even if the error text mentions self-signed. Note whether issuer equals subject on the leaf (true self-signed) or points to a private CA.
Python Requests
import ssl, socket
host, port = "api.example.com", 443
ctx = ssl.create_default_context()
with socket.create_connection((host, port)) as sock:
with ctx.wrap_socket(sock, server_hostname=host) as tls:
cert = tls.getpeercert()
print(cert.get("issuer"), cert.get("notAfter"))
A ssl.SSLCertVerificationError with self-signed in the message should match OpenSSL issuer lines.
Compare trust store
curl -vI --connect-timeout 10 "https://$HOST/" 2>&1 | head -n 40
If curl fails with self signed certificate but a browser on a managed laptop works, the laptop likely has an extra root installed. Your servers need the same CA file via SSL_CERT_FILE, REQUESTS_CA_BUNDLE, or OS trust update—not disabled verification.
Fix checklist
- Identify the terminator (ingress, reverse proxy, SaaS edge) presenting the untrusted chain.
- If
notAfteris in the past, renew or reissue from your CA or ACME workflow, then deploy full chain PEM. - If dates are valid but untrusted, install the issuing CA (and any intermediate) into client trust stores that need it—or replace the listener cert with a publicly trusted one for internet-facing names.
- Ensure SNI attaches the correct certificate; remove default self-signed fallback on shared listeners.
- Reload or restart every worker that caches TLS context (nginx, Envoy, Java, Node) after file changes.
- Re-run the same Python, curl, or SDK call that failed without bypass flags.
- Record issuer DN and serial in the ticket; add the hostname to live expiry monitoring before close.
If clients run inside Kubernetes, compare the in-cluster Service DNS name with the certificate SAN. A cert valid for an external edge name fails verify when workloads call https://myservice.namespace.svc unless you issue a cert for that internal pattern or terminate TLS on the public ingress only.
For partner integrations, ship a ca.pem in documentation and automate rotation reminders on that CA’s notAfter, not only the leaf customers see today. After any CA migration, regression-test old clients before you revoke prior intermediates.
Post-incident, scan repositories for verify=False, InsecureSkipVerify, rejectUnauthorized: false, and NPM NODE_TLS_REJECT_UNAUTHORIZED overrides added during debugging. Those lines are easier to remove when the server chain is already publicly trusted.
When to use automation
Disk inventories and “cert expires next month” spreadsheets do not show what strangers receive after a partial deploy. Use an external free TLS check on the exact hostname and port clients call, including redirects, so you catch untrusted chains and past notAfter on the live wire.
Pair certificate expiry with registrar visibility where RDAP publishes dates, so domain revalidation is not blocked late in renewal. Scheduled monitoring with alerts at 30, 14, 7, and 1 day before expiry gives runway when OV approvals or ACME DNS challenges slip—without replacing your CA process.
Frequently asked questions
Can I fix this by setting verify=False in production?
Disabling verification removes TLS authentication entirely. Import the correct CA or install a publicly trusted cert instead. Short-lived debug on an isolated host is different from shipping bypasses in application code.
Why do only some regions fail?
Any pool member can serve stale or default certs. Test each load-balancer target IP or regional hostname with the same SNI flag. Uneven reloads after secret rotation are a common pattern.
Does this relate to mTLS client certificates?
Client auth errors usually mention peer certificate or handshake failure, not self-signed server chains. Still inspect server chain first; dual problems happen after rushed renewals.
When is importing an internal CA the right fix?
When the endpoint is intentionally private—mesh gRPC, internal APIs, or lab registries—and you control all clients. Public websites should use publicly trusted chains instead of asking users to install roots.
See what the server presents right now
Free live certificate check — no signup.