The error message is unambiguous: SSL certificate problem: unable to get local issuer certificate. The tool you are running — curl, git, Python requests, Node.js, or something else entirely — attempted to verify the server’s certificate chain and could not locate a trusted issuer for one of the certificates in that chain. Path validation failed.
Three distinct conditions produce this. The server is not sending its full certificate chain, so the client receives a leaf certificate with no intermediate to follow the chain upward. The client’s CA bundle does not contain the root or intermediate needed to complete validation, either because it is outdated or because the certificate was issued by a private or corporate CA that is not in the public bundle. Or a corporate SSL inspection proxy is presenting its own certificate, signed by an internal CA that is not in any standard bundle.
The fix is different for each. Applying the wrong fix wastes time and occasionally makes things worse. This guide diagnoses which condition you have and provides the targeted resolution for each tool and environment.
What the Error Is Telling You: Chain Path Validation
When any TLS client connects to a server, it receives the server’s certificate chain — ideally the leaf certificate followed by one or more intermediate CA certificates. The client then walks the chain: starting at the leaf, it looks for the issuer of each certificate and verifies the cryptographic signature. This continues until the client finds a certificate in its own trusted CA bundle (the local issuer) that matches the issuer of the current chain link.
The error fires when that walk reaches a dead end. The client holds a certificate whose issuer it cannot find anywhere in its local bundle. The chain is either incomplete (the server did not send an intermediate that would bridge the gap) or the root itself is not in the client’s bundle (because it is from a private CA, or the bundle is outdated and missing a newer root).
The word local in the error message is precise. It is not the server’s issuer that is missing. It is the client’s local copy of that issuer. Everything on the server side could be perfectly configured, and you still see this error if the client does not have the right CA bundle.
This is why the error appears in curl, git, and Python on systems where the same URL opens fine in Chrome. Browsers use the operating system trust store and get regular root certificate updates via OS patches. curl uses a compiled-in CA bundle (or a system bundle on Linux). git on Windows uses its own bundled OpenSSL CA bundle by default. Python’s requests uses certifi. None of these automatically receive the same updates as the browser-facing OS trust store.
Diagnose Before You Fix: Two Commands That Tell You Everything
Before touching any configuration, run the server-side diagnostic. It tells you whether the problem is on the server (missing intermediate) or on the client (missing root in bundle).
| # Test the certificate chain the server is actually sending:
$ openssl s_client -connect hostname:443 -showcerts 2>/dev/null
# Count the certificates in the output: $ openssl s_client -connect hostname:443 -showcerts 2>/dev/null | grep -c ‘BEGIN CERTIFICATE’
# A correct chain returns 2 or 3. A return of 1 means the server # is only sending the leaf certificate — the intermediate is missing. # This is the server-side problem. Fix is on the server.
# If the chain count is correct (2-3), the problem is in your client bundle. # Check which CA bundle your tool is actually using: $ curl –version | grep -i ‘SSL\|CAfile\|CApath’ $ python3 -m certifi # shows path certifi is using $ git config –list | grep ssl # shows git ssl configuration |
If openssl s_client returns only 1 certificate, stop here. The fix is on the server and no amount of client-side bundle updating will resolve it. The server administrator needs to concatenate the intermediate certificate into the served chain. For servers you control, see the server-side fix section.
Root Cause Map: Match Your Situation to the Right Fix
| Chain count | Environment | Most likely cause | Fix section |
| 1 (leaf only) | Any tool, any OS | Server not sending intermediate certificate | Server-Side Fix: Complete the Certificate Chain |
| 2 or 3 | curl on Linux | System CA bundle outdated or missing a newer root | Client Fix: Update the CA Bundle (Linux) |
| 2 or 3 | git on Windows | Git uses its own OpenSSL bundle, separate from Windows trust store | Client Fix: Git on Windows — Switching SSL Backend |
| 2 or 3 | Python requests / pip | certifi package is outdated or corporate CA not appended | Client Fix: Python and pip |
| 2 or 3 | Corporate network, any tool | SSL inspection proxy presenting its own certificate | Client Fix: Corporate CA Certificate |
| 2 or 3 | Internal / private CA cert | CA is not in any public bundle | Client Fix: Adding a Private CA to Trust |
| 2 or 3 | macOS, fresh Python install | Python on macOS uses its own SSL, not system Keychain | Client Fix: Python on macOS |
Server-Side Fix: Complete the Certificate Chain
If the server is returning only one certificate, the intermediate is not being served. Every browser and command-line tool that does strict chain validation will fail against this server. The fix is assembling the full chain on the server.
| # Assemble the full chain for Nginx:
# Order: leaf cert first, then intermediate(s), then optionally root $ cat yourdomain.com.crt intermediate.crt > yourdomain.com.fullchain.crt
# In nginx.conf or site config: ssl_certificate /etc/ssl/certs/yourdomain.com.fullchain.crt; ssl_certificate_key /etc/ssl/private/yourdomain.com.key;
# Test and reload: $ nginx -t && systemctl reload nginx
# For Apache 2.4.8+: single bundle file works SSLCertificateFile /etc/ssl/certs/yourdomain.com.fullchain.crt
# For older Apache: separate directives SSLCertificateFile /etc/ssl/certs/yourdomain.com.crt SSLCertificateChainFile /etc/ssl/certs/intermediate.crt
# Verify the chain is now complete: $ openssl s_client -connect yourdomain.com:443 -showcerts 2>/dev/null | grep -c ‘BEGIN CERTIFICATE’ # Should now return 2 or 3 |
Where do you get the intermediate certificate? Download it from your CA’s website. Every public CA publishes their intermediate certificates, usually on a Repository page, an Intermediate CA page, or linked in the issuance email. The filename is typically something like DigiCertTLSHybridECCSHA3842020CA1.crt or Sectigo RSA Domain Validation Secure Server CA.crt. You can also extract it from the chain: connect to a site that is serving it correctly (run openssl s_client and look for the second certificate block) and copy the PEM content.
Client Fix: Updating the CA Bundle on Linux
On Debian, Ubuntu, and derivatives, the system CA bundle is maintained by the ca-certificates package. On RHEL, CentOS, Rocky, and Fedora, it is maintained by ca-certificates or ca-trust. If the server is correctly sending its chain but curl or other tools still fail, the root CA is not in the system bundle. Update it:
| # Debian / Ubuntu:
$ sudo apt update && sudo apt install –only-upgrade ca-certificates $ sudo update-ca-certificates
# RHEL / CentOS / Rocky: $ sudo yum update ca-certificates $ sudo update-ca-trust
# After updating, retest: $ curl -I https://hostname
# If a specific CA is still missing after the package update, # add the certificate manually:
# Debian / Ubuntu: $ sudo cp corporate-or-custom-ca.crt /usr/local/share/ca-certificates/ $ sudo update-ca-certificates
# RHEL / CentOS / Rocky: $ sudo cp corporate-or-custom-ca.crt /etc/pki/ca-trust/source/anchors/ $ sudo update-ca-trust extract |
Client Fix: Git on Windows — SSL Backend and Bundle
Git on Windows ships with its own OpenSSL and CA bundle, compiled into the git-core binaries. By default, it uses this bundled CA list rather than the Windows certificate store. This means certificates that are perfectly trusted by Windows (including corporate MITM CA certificates deployed via group policy) are invisible to git unless you either switch to the Windows SSL backend or manually update git’s bundle.
| # Option 1: Switch git to use Windows SChannel (uses Windows trust store):
$ git config –global http.sslBackend schannel
# This makes git respect the Windows certificate store, including # corporate CA certificates deployed by IT via group policy. # This is the cleanest fix for corporate environments.
# Option 2: Point git at a specific CA bundle file: $ git config –global http.sslCAInfo /path/to/your/ca-bundle.crt
# Option 3: Append the missing CA to git’s own bundle: # Find git’s bundle location: $ git config –list –show-origin | grep cainfo # If empty, git uses its compiled-in bundle. # Find it: $ git config –global –list # Bundle is typically at: C:\Program Files\Git\mingw64\etc\ssl\certs\ca-bundle.crt
# Append missing CA: $ cat corporate-ca.crt >> ‘C:\Program Files\Git\mingw64\etc\ssl\certs\ca-bundle.crt’
# Verify the fix: $ git ls-remote https://hostname/repo.git |
Switching to SChannel (git config –global http.sslBackend schannel) is the correct long-term fix for Windows corporate environments. The system trust store is maintained by IT, receives group policy updates, and handles all corporate CA certificates automatically. The alternative of updating git’s own bundle file is overwritten on every git upgrade. SChannel persists through upgrades.
Client Fix: Python requests, pip, and certifi
Python’s requests library uses the certifi package as its CA bundle rather than the system trust store. certifi is a Python package that must be updated independently. This is the most common cause of the error in Python environments, particularly after a new Python install or when the certifi package has not been updated in a while.
| # Update certifi:
$ pip install –upgrade certifi
# Verify what bundle Python is actually using: $ python3 -m certifi # Returns: /path/to/lib/python3.x/site-packages/certifi/cacert.pem
# For corporate or private CA certificates, append to certifi’s bundle: $ cat $(python3 -m certifi) # shows current bundle contents $ cat corporate-ca.crt >> $(python3 -m certifi)
# Or set environment variable (cleaner than modifying certifi directly): $ export REQUESTS_CA_BUNDLE=/path/to/combined-bundle.pem $ export SSL_CERT_FILE=/path/to/combined-bundle.pem
# In code — point to a specific CA file: # import requests # response = requests.get(‘https://hostname’, verify=’/path/to/ca.crt’)
# pip also needs the CA bundle for package downloads: $ pip install –cert /path/to/corporate-ca.crt package-name # Or configure globally: $ pip config set global.cert /path/to/corporate-ca.crt |
Client Fix: Python on macOS
This is a category of its own because macOS Python behaves differently from every other platform. Python installed from python.org on macOS ships with its own OpenSSL that is completely independent of the macOS system OpenSSL and does not access the macOS Keychain. Running this Python and making an HTTPS request fails for sites whose root CA is in the system Keychain but not in certifi.
| # The fastest fix: run Python’s own certificate installer
# (ships with Python from python.org) $ open /Applications/Python\ 3.12/Install\ Certificates.command # Replace 3.12 with your actual Python version
# Or from Terminal: $ /Applications/Python\ 3.12/Install\ Certificates.command
# What this script does: # 1. Installs/upgrades certifi # 2. Creates a symlink so Python uses certifi’s bundle
# If you installed Python via Homebrew, it links against # Homebrew’s OpenSSL which does use the system trust store. # Homebrew Python typically does not have this problem.
# Verify after running the installer: $ python3 -c ‘import urllib.request; urllib.request.urlopen(“https://google.com”)’ # Should complete without error |
Client Fix: Corporate SSL Inspection Proxy
If you are on a corporate network, the error often has nothing to do with the actual server’s certificate. The corporate proxy decrypts your HTTPS traffic and re-signs it with a corporate CA certificate. Every tool needs that corporate CA in its trust bundle or it fails chain validation. Browsers usually work because IT deploys the corporate CA to the OS trust store. Command-line tools use their own bundles and do not automatically pick it up.
Get the corporate root CA certificate from your IT department, or export it from a browser on the same network (Chrome: Settings, Privacy, Security, Manage certificates; Firefox: Settings, Privacy, View Certificates). Save it as a .crt or .pem file, then add it to each tool:
| # Linux system-wide (all tools that use the system bundle):
$ sudo cp corporate-root-ca.crt /usr/local/share/ca-certificates/ $ sudo update-ca-certificates # Debian/Ubuntu
# Python / pip: $ cat $(python3 -m certifi) corporate-root-ca.crt > combined-bundle.pem $ export REQUESTS_CA_BUNDLE=$(pwd)/combined-bundle.pem
# git (Windows — switch to SChannel to use Windows trust store): $ git config –global http.sslBackend schannel
# git (Linux/Mac — append to bundle): $ git config –global http.sslCAInfo /path/to/combined-bundle.pem
# curl (one-off): $ curl –cacert corporate-root-ca.crt https://hostname
# curl (permanent, via config file at ~/.curlrc): $ echo ‘cainfo = /path/to/corporate-root-ca.crt’ >> ~/.curlrc
# npm: $ npm config set cafile /path/to/combined-bundle.pem |
A Real-World Example: The Let’s Encrypt DST Root CA X3 Expiry (2021)
In September 2021, the DST Root CA X3 root certificate expired. This CA had been used to cross-sign Let’s Encrypt’s ISRG Root X1, which gave ISRG Root X1 trust on old systems that had ISRG Root X1 in their bundle but relied on the cross-signature for additional compatibility. When DST Root CA X3 expired, OpenSSL 1.0.x on Linux treated the cross-signed path as broken rather than falling back to the direct ISRG Root X1 trust.
The result: millions of HTTPS requests from Python scripts, curl commands, and git operations on older Linux systems suddenly started producing exactly this error for sites using Let’s Encrypt certificates. The certificates were valid. The servers were correctly configured. The client tools were behaving as designed. The problem was that the client’s OpenSSL version had a specific behavior around expired CA cross-signatures that made it choose the broken path.
The fix was either updating OpenSSL to a version that handled the expired cross-signature gracefully, updating the ca-certificates package to remove the expired DST Root CA X3 entry, or telling OpenSSL not to use expired roots. The entire incident highlighted why being precise about whether the problem is in the client’s bundle versus the server’s chain matters enormously — and why the diagnostic step (count the certificates the server sends) should always come before any fixing.
The Let’s Encrypt DST expiry also illustrated a subtlety in how OpenSSL builds certificate paths. OpenSSL by default tries all paths and picks one — but in certain versions it prefers the longest path, which in this case led through the expired DST Root CA X3. Newer OpenSSL versions handle this more gracefully. If you are running OpenSSL 1.0.x anywhere in production, upgrading is overdue regardless of this specific issue.
The Fix That Is Not a Fix: Disabling SSL Verification
Every tool that produces this error has a flag to bypass it. curl has -k or –insecure. git has http.sslVerify false. Python requests has verify=False. npm has strict-ssl false. These flags disable certificate chain validation entirely, turning HTTPS into a transport with encryption but no authentication. An attacker positioned between your client and the server can present any certificate and the tool will accept it.
There is one context where these flags are defensible: diagnosing whether certificate validation is the specific cause of a connection failure. Run once with -k or verify=False to confirm the connection works when validation is disabled. Then fix the underlying cause and remove the flag. Any code committed to a repository, any configuration in a production system, or any CI pipeline step with these flags enabled is a security vulnerability regardless of what other controls exist.
git config –global http.sslVerify false is permanent and global. It disables certificate validation for every git operation on the machine, not just the one you were troubleshooting. If you ran this command at some point, verify it is not still in your global git config: run git config –global –list and check for http.sslVerify. Remove it with git config –global –unset http.sslVerify.
Quick Diagnostics Reference by Tool
| Tool | Where it gets its CA bundle | Check command | Fix path |
| curl (Linux) | System CA bundle (/etc/ssl/certs/) | curl –version | grep CAfile | apt/yum update ca-certificates; or –cacert flag |
| curl (macOS) | macOS system Keychain via LibreSSL | curl –version | grep SSL | Add CA to Keychain via security add-trusted-cert |
| curl (Windows) | System Certificate Store (via SChannel) or bundled on some builds | curl –version | grep SSL | Install CA to Windows cert store; or –cacert flag |
| git (Windows) | Bundled OpenSSL CA bundle by default | git config –list | grep ssl | Switch to SChannel: git config –global http.sslBackend schannel |
| git (Linux/Mac) | System CA bundle | git config –list | grep ssl | Update system ca-certificates package; or http.sslCAInfo |
| Python requests | certifi package bundle | python3 -m certifi | pip upgrade certifi; append custom CA to bundle |
| Python urllib | certifi (via ssl.DefaultVerifyPaths or certifi) | ssl.get_default_verify_paths() | Same as requests; or ssl.create_default_context(cafile=…) |
| pip | certifi or –cert flag | pip config list | pip config set global.cert; or –cert per command |
| npm | Bundled CA list or NODE_EXTRA_CA_CERTS | npm config get cafile | npm config set cafile; or NODE_EXTRA_CA_CERTS env variable |
| Java / JVM tools | Java truststore (cacerts or JAVA_HOME/lib/security/cacerts) | keytool -list -cacerts | keytool -importcert -alias name -file ca.crt -cacerts |
Frequently Asked Questions
What exactly does ‘unable to get local issuer certificate’ mean?
It means OpenSSL’s certificate path validation reached a point in the chain where it could not find the issuer of the current certificate in the client’s local CA bundle. Either the server did not send an intermediate certificate that would allow the chain to be followed upward, or the server sent the full chain but the root CA at the top is not present in the client’s trusted bundle. The word local refers to the client’s own copy of the CA bundle — the file or store the tool uses to verify certificate issuers.
Why does the URL work in a browser but fail with curl or git?
Browsers and command-line tools use different CA bundles. Browsers use the operating system trust store, which receives regular updates via OS patches and (for Windows and macOS) corporate certificates pushed via group policy or MDM. curl, git, Python, and Node.js typically use their own CA bundles that do not automatically receive these updates. When a corporate CA certificate is deployed to the OS trust store by IT, it is invisible to these tools until you explicitly configure them to trust it as well.
How do I fix this in a Docker container?
Docker containers based on minimal images often have outdated or minimal CA bundles. For Alpine Linux: run apk add –no-cache ca-certificates and update-ca-certificates. For Debian-based images: run apt-get update && apt-get install -y ca-certificates and update-ca-certificates. To add a custom CA to a Docker image, copy the certificate into the container and run update-ca-certificates in the Dockerfile. For corporate proxy CAs, passing the CA bundle as a build argument or mounting it as a volume during development are common patterns.
The server is sending the full chain. Why does the error persist?
If openssl s_client confirms the server sends 2 or 3 certificates but the error persists, the root CA at the top of the chain is not in your client’s trusted bundle. This happens with private or corporate CAs (never in public bundles by design), with newer public root CAs added after your bundle was compiled or last updated, or when a corporate proxy is intercepting and replacing the server certificate with its own. Update your CA bundle, or if the root is from a private CA, add it explicitly using the per-tool instructions in this guide.
Is this the same as ERR_CERT_AUTHORITY_INVALID in Chrome?
The same underlying condition — the browser cannot trace the certificate chain to a trusted root — produces both. ERR_CERT_AUTHORITY_INVALID is Chrome’s error code for exactly what OpenSSL calls unable to get local issuer certificate. The difference is that Chrome recovers more gracefully in some cases (it performs AIA fetching, downloading missing intermediates from URLs in the certificate’s Authority Information Access extension) while curl and other OpenSSL-based tools typically do not. A site that works in Chrome but fails in curl may have a missing intermediate that Chrome fetched automatically.
