Error message
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate
Also printed as
- [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate
Python appends the C source location, e.g. "(_ssl.c:1082)". Not to be confused with "self-signed certificate in certificate chain", which means a CA chain ending in an untrusted root. OpenSSL 1.1.1 spells it "self signed".
Python ssl module (OpenSSL) — ssl.SSLCertVerificationError, X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT (18)
What it means
The server presented a single self-signed certificate (its issuer is itself) that is not in this context's trusted CA set. There is no CA to look up, so OpenSSL fails at depth 0. Typical for development servers, appliances and fresh installs; the remedy is to trust that certificate deliberately or to replace it.
Why it happens
A development or test server with a generated certificate
commonLocal servers started with an ad-hoc certificate (Flask's ssl_context="adhoc", uvicorn with a generated key, docker-compose stacks) present a fresh self-signed certificate that nothing trusts.
A device or product with its factory certificate
commonNAS boxes, routers, Proxmox, Elasticsearch, Jenkins and many vendor appliances start with a self-signed certificate until an administrator installs a proper one.
A proxy serves its built-in default certificate
occasionalIngress controllers and reverse proxies answer unknown host names with a self-signed default certificate; then the real problem is routing or SNI.
How to fix it
- 1.
Trust this one certificate for this client
Load the server's own certificate as cafile. Hostname and date checks stay on, and only this exact certificate is accepted.
python import ssl, urllib.request # Trust exactly this certificate (fetched and fingerprint-checked out of band) ctx = ssl.create_default_context(cafile="api.example.com.pem") urllib.request.urlopen("https://api.example.com:8443/", context=ctx) - 2.
Fetch the certificate and check its fingerprint
Compare the SHA-256 fingerprint with the value shown on the device's console or by its administrator before trusting the file.
bash openssl s_client -connect api.example.com:8443 -servername api.example.com </dev/null 2>/dev/null \ | openssl x509 -out api.example.com.pem openssl x509 -in api.example.com.pem -noout -fingerprint -sha256 # compare with the server's console - 3.
Development: use a local CA instead
A local CA lets you trust one root and issue properly signed certificates for every dev host, so no client needs per-certificate exceptions.
bash # Development: one local CA, many certificates (mkcert installs the CA for you) mkcert -install mkcert api.example.test export SSL_CERT_FILE="$(mkcert -CAROOT)/rootCA.pem" - 4.
Temporary diagnostic: turn verification off in a test
check_hostname = False and CERT_NONE disable every check and accept any certificate, including one presented by an attacker. Use it only to confirm that trust is the only problem, then remove it.
python # Temporary diagnostic only: proves trust is the sole problem. Never ship this. import ssl ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE