CERTIFICATE_VERIFY_FAILED 🔌 API

SSL: CERTIFICATE_VERIFY_FAILED / unable to get local issuer certificate

The client couldn’t verify the server’s TLS certificate chain, so it refused the HTTPS connection.

Meaning

TLS clients check that the server certificate chains up to a trusted root CA, matches the hostname and isn’t expired. The error means one of those checks failed — very often the server is missing its intermediate certificate, or the client machine has an outdated / missing CA bundle (common with Python on macOS and behind corporate SSL-inspecting proxies).

Turning verification off makes the error go away but removes protection against man-in-the-middle attacks — fix the chain or the trust store instead.

Common causes

  • Server sends only its leaf certificate, without the intermediate chain
  • Client CA bundle missing/outdated (Python.org installer on macOS, old Docker base images)
  • Corporate proxy re-signs HTTPS with its own root CA that the client doesn’t trust
  • Self-signed certificate in development
  • Certificate expired or hostname doesn’t match (*.example.com doesn’t cover example.com)
  • System clock badly wrong

⚡ Quick fix

  1. Test the server chain: openssl s_client -connect host:443 -servername host -showcerts
  2. On the server, install the full chain (fullchain.pem, not cert.pem)
  3. Update client CA certificates (pip install -U certifi, apt install ca-certificates)
  4. macOS + python.org Python: run “Install Certificates.command”
  5. Behind a corporate proxy, point the client at the company root CA

Detailed fix by platform

Python

  1. Use certifi’s bundle or your company CA:
    python
    import certifi, requests
    requests.get("https://api.example.com", verify=certifi.where())
    # corporate proxy root CA:
    requests.get("https://api.example.com", verify="/etc/ssl/certs/corp-root.pem")

Node.js

  1. Trust an extra CA (corporate proxy) without disabling verification:
    bash
    export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/corp-root.pem
    node app.js
    # never ship NODE_TLS_REJECT_UNAUTHORIZED=0 to production

Nginx

  1. Serve the full chain:
    nginx
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;   # not cert.pem
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

PHP

  1. cURL error 60: set curl.cainfo / openssl.cafile in php.ini to a current cacert.pem.

Git

  1. git config --global http.sslCAInfo /path/to/corp-root.pem instead of http.sslVerify false.

Code examples

Check the chain the server sends

bash
openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \
  | grep -E 'Verify return code|s:|i:'
# "Verify return code: 21 (unable to verify the first certificate)" → intermediate missing on the server

How to diagnose

  1. Server chain — Does openssl s_client show the full chain and return code 0?
  2. Hostname & dates — Does the cert match the host and is it within its validity period?
  3. Client trust store — Is the CA bundle current? Which one does this runtime use?
  4. Network — Is a proxy/antivirus intercepting TLS?
  5. Clock — Is the system time correct?

🧠 Still stuck? Analyze your error

Paste the full message, response headers or stack trace — we'll detect the platform and point to the most likely cause.