Fix curl SSL Certificate Problem Behind Corporate Proxy on Linux

Fix curl error 60 and similar HTTPS failures on Linux when corporate SSL inspection replaces public certificates with a private root CA trusted on Windows but missing from the Linux trust store.

Published

Updated

Read time 13 min read

Reviewed byDeepak Prasad

Fix curl SSL Certificate Problem Behind Corporate Proxy on Linux

You run curl, git clone, pip install, or docker pull on a corporate network and hit:

output
curl: (60) SSL certificate problem: unable to get local issuer certificate

In most corporate-network cases, this is not a broken curl package. It means the TLS certificate presented to your Linux host was signed by a corporate inspection CA your system does not trust yet. Windows and the browser often work because IT already pushed that root CA to managed PCs. Linux VMs, WSL, CI runners, and containers usually start with only public CAs.

This guide walks through diagnosis, installing the corporate root CA on Debian-family and RHEL-family distros, verifying the fix, and pointing common developer tools at the updated bundle. The Rocky Linux 10.2 lab output below comes from a real Zscaler inspection environment; the same steps apply when the issuer shows Netskope, Fortinet, Palo Alto, Cisco Umbrella, Cloudflare Gateway, or an internal AD CS CA.

Tested on: Rocky Linux 10.2 (Red Quartz); kernel 6.12.0-211.16.1.el10_2.0.1.x86_64; curl 8.12.1; OpenSSL 3.x.

IMPORTANT
This article covers trusting a corporate SSL inspection root CA on Linux clients so HTTPS verification succeeds again. It does not cover fixing server-side missing intermediate chains, renewing Let's Encrypt on your own web server, or permanently disabling TLS verification. For inspecting certificates and chains, see view a certificate with OpenSSL.

Quick answer

Family Install path Refresh trust Default bundle for tools
Ubuntu, Debian /usr/local/share/ca-certificates/*.crt sudo update-ca-certificates /etc/ssl/certs/ca-certificates.crt
RHEL, Rocky, AlmaLinux, Fedora /etc/pki/ca-trust/source/anchors/*.crt sudo update-ca-trust extract /etc/pki/tls/certs/ca-bundle.crt

Copy your organization's inspection root CA in PEM format, run the refresh command, then retry curl -v https://example.com. If only one application still fails, add the bundle path to that tool's CA setting (Git, Python, Node.js, Java, or containers).


Why this error happens

On the public internet, a site like github.com normally presents a certificate signed by a public CA (DigiCert, Sectigo, Google Trust Services, Let's Encrypt, and others). Your Linux trust store already includes those public roots, so verification succeeds.

Corporate secure web gateways and firewalls often perform SSL/TLS inspection (HTTPS interception):

  1. Your client starts TLS to what it thinks is github.com.
  2. The security appliance terminates TLS, inspects the traffic, and opens its own TLS session to the real server.
  3. The appliance returns a replacement certificate signed by a private corporate CA.
  4. If that corporate CA is not in the Linux trust store, OpenSSL reports unable to get local issuer certificate and curl exits with code 60.

Windows workstations and browsers frequently trust the inspection CA already (Group Policy, Zscaler Client Connector, Netskope client, and similar). A Rocky Linux VM, Ubuntu server, WSL distro, or Docker container may not inherit that trust automatically.

Common products or platforms involved in this pattern include Zscaler ZIA, Netskope, Cisco Umbrella, Cloudflare Gateway, Fortinet FortiGate SSL inspection, Palo Alto Networks decryption, Check Point HTTPS inspection, Sophos firewall scanning, and Microsoft AD CS–issued forward-trust CAs.


Symptoms across tools

The underlying failure is the same missing trust anchor; only the error text changes:

Tool Typical message
curl curl: (60) SSL certificate problem: unable to get local issuer certificate
Git SSL certificate problem: unable to get local issuer certificate
Python / pip SSLError: CERTIFICATE_VERIFY_FAILED
npm UNABLE_TO_GET_ISSUER_CERT_LOCALLY
Go x509: certificate signed by unknown authority
Codex CLI Login or device-auth HTTPS/WebSocket failures after install

Reinstalling curl or upgrading CA packages without adding the corporate root does not fix inspection failures.


Step 1 — Check whether all HTTPS sites fail

Start with proxy environment variables. They change which path curl takes:

bash
env | grep -i proxy
output
https_proxy=http://proxy.example.com:8080
HTTPS_PROXY=http://proxy.example.com:8080

If HTTP_PROXY or HTTPS_PROXY is set, curl may go through an explicit forward proxy. If they are unset, the network may still perform transparent TLS inspection. During diagnosis, add --noproxy '*' to mirror what tools like openssl s_client see on a direct TLS connection. For proxy variable setup, see set up http_proxy and https_proxy.

If routing and DNS look healthy but HTTPS still fails, work through the layered checks in check internet connection from the Linux command line before trusting a new root CA.

Test a site that may bypass inspection in your environment:

bash
curl -Iv --noproxy '*' --max-time 15 https://www.google.com
output
*  subject: CN=www.google.com
*  issuer: C=US; O=Google Trust Services; CN=WE2
*  SSL certificate verify ok.

Google still used a public CA in our lab, so verification succeeded even before we installed the corporate root.

Test a site your policy likely inspects:

bash
curl -Iv --noproxy '*' --max-time 15 https://github.com

Before installing the corporate CA, the same command failed:

output
* TLSv1.3 (OUT), TLS alert, unknown CA (560):
* SSL certificate problem: unable to get local issuer certificate
curl: (60) SSL certificate problem: unable to get local issuer certificate

After we trusted the corporate root (Step 5), the issuer changed to the inspection CA and verification succeeded:

output
*  subject: CN=github.com; O=Zscaler Inc.; OU=Zscaler Inc.
*  issuer: C=US; ST=California; O=Zscaler Inc.; OU=Zscaler Inc.; CN=Zscaler Intermediate Root CA (zscaler.net) (t)
*  SSL certificate verify ok.

If every HTTPS URL fails, check system time with timedatectl, confirm chrony or NTP has the clock in sync, and rule out a corrupt CA package or an unrecognized man-in-the-middle before trusting a new root. If only selected domains fail, SSL inspection is the likely cause.


Step 2 — Identify the certificate issuer

openssl s_client shows who signed the certificate your host actually receives. Pick a URL that fails under curl.

bash
openssl s_client -connect github.com:443 -servername github.com </dev/null 2>/tmp/cert.log | openssl x509 -noout -subject -issuer -dates
output
subject=CN=github.com, O=Zscaler Inc., OU=Zscaler Inc.
issuer=C=US, ST=California, O=Zscaler Inc., OU=Zscaler Inc., CN=Zscaler Intermediate Root CA (zscaler.net) (t)
notBefore=Jul  5 17:01:57 2026 GMT
notAfter=Jul 18 02:14:50 2026 GMT

Read the issuer line:

  • Public CA (Google Trust Services, DigiCert, Let's Encrypt, Sectigo) — normal direct TLS or bypassed inspection.
  • Vendor or company name (Zscaler, Fortinet, Netskope, Palo Alto, or your organization) — SSL inspection; install the documented corporate root CA.

The verification log at the bottom confirms the missing anchor before you fix trust:

bash
grep -E 'verify error|depth=' /tmp/cert.log | head -4
output
depth=2 C=US, ST=California, O=Zscaler Inc., OU=Zscaler Inc., CN=Zscaler Intermediate Root CA (zscaler.net)
verify error:num=20:unable to get local issuer certificate
depth=1 C=US, ST=California, O=Zscaler Inc., OU=Zscaler Inc., CN=Zscaler Intermediate Root CA (zscaler.net) (t)
depth=0 CN=github.com, O=Zscaler Inc., OU=Zscaler Inc.

curl -v also prints which CA file it uses — useful when a tool ignores your new certificate:

bash
curl -v --noproxy '*' --max-time 10 https://github.com 2>&1 | grep -E 'CAfile:|CApath:'
output
*  CAfile: /etc/pki/tls/certs/ca-bundle.crt
*  CApath: none

On Debian and Ubuntu the default CAfile is /etc/ssl/certs/ca-certificates.crt instead.


Step 3 — Get the correct corporate root CA

Do not export the short-lived leaf certificate for github.com or auth.openai.com. Install the root CA (or the forward-trust CA your security team documents) that signs inspected traffic.

Sources:

  • IT or security team portal (preferred)
  • Windows: certmgr.msc → Trusted Root Certification Authorities → Certificates → right-click the corporate root → All Tasks → Export → Base-64 encoded X.509 (.CER)
  • macOS: Open Keychain Access, check the System and login keychains, find your organization's proxy or root CA certificate, then export it as PEM or CER

You need PEM text (-----BEGIN CERTIFICATE----------END CERTIFICATE-----). If export gives binary DER, convert before installing:

bash
openssl x509 -inform DER -in company-root-ca.cer -out company-root-ca.crt
output
(no output on success)

Check the file format when unsure:

bash
file company-root-ca.cer
output
company-root-ca.cer: PEM certificate

Step 4 — Install the CA on Ubuntu and Debian

Install tooling if the image is minimal:

bash
sudo apt update
sudo apt install -y ca-certificates openssl curl
output
ca-certificates is already the newest version.
curl is already the newest version.
openssl is already the newest version.
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.

Copy the PEM file into the local trust directory. The filename must end with .crt:

bash
sudo cp company-root-ca.crt /usr/local/share/ca-certificates/company-root-ca.crt
output
(no output on success)

Rebuild the system bundle:

bash
sudo update-ca-certificates

A successful run appends your CA and reports one added certificate:

output
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.

Verify HTTPS again:

bash
curl -Iv --max-time 15 https://github.com
output
*  SSL certificate verify ok.

On an inspected network, the issuer may show your proxy or security gateway, such as Zscaler, Fortinet, Netskope, or an internal CA.

Confirm OpenSSL can build a chain against the Debian bundle:

bash
openssl s_client -connect github.com:443 -servername github.com -CAfile /etc/ssl/certs/ca-certificates.crt </dev/null 2>&1 | grep 'Verify return code'

After update-ca-certificates, you want Verify return code: 0 (ok).

Point long-running tools at the Debian bundle when needed:

bash
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
output
(no output on success)

Step 5 — Install the CA on RHEL, Rocky, AlmaLinux, and Fedora

On Rocky Linux 10.2 we reproduced the failure, installed the corporate root, and verified the fix.

Install packages:

bash
sudo dnf install -y ca-certificates openssl curl p11-kit-trust
output
Package ca-certificates-2025.2.86-1.el10.noarch is already installed.
Package openssl-1:3.5.5-4.el10_2.x86_64 is already installed.
Package curl-8.12.1-4.el10.x86_64 is already installed.
Package p11-kit-trust-0.26.2-1.el10.x86_64 is already installed.
Dependencies resolved.
Nothing to do.
Complete!

Copy the PEM certificate into the system trust anchors directory:

bash
sudo cp /root/zscaler-root-ca.cer /etc/pki/ca-trust/source/anchors/zscaler-root-ca.crt
output
(no output on success)

Rebuild extracted bundles (including /etc/pki/tls/certs/ca-bundle.crt):

bash
sudo update-ca-trust extract
output
(no output on success)

Confirm the anchor is trusted:

bash
trust list | grep -i zscaler -A3
output
label: Zscaler Root CA
    trust: anchor
    category: authority

Retry HTTPS:

bash
curl -Iv --noproxy '*' --max-time 15 https://github.com
output
*  SSL certificate verify ok.

Confirm OpenSSL can build a chain to the system bundle:

bash
openssl s_client -connect github.com:443 -servername github.com -CAfile /etc/pki/tls/certs/ca-bundle.crt </dev/null 2>&1 | grep 'Verify return code'
output
Verify return code: 0 (ok)
NOTE
Older RHEL 7-era guides may mention update-ca-trust force-enable. On Rocky Linux 10 this command returned unknown command in our test:
bash
update-ca-trust force-enable
output
Error: unknown command: 'force-enable', see 'update-ca-trust --help' for usage.

On current RHEL-family systems, place the certificate under /etc/pki/ca-trust/source/anchors/ and run update-ca-trust extract.


Step 6 — Fix tools that ignore the system CA store

update-ca-certificates and update-ca-trust fix anything that reads the OS OpenSSL bundle (curl, wget, dnf, yum, and many Go binaries). These tools often keep their own trust store. Set persistent exports in /etc/profile.d/corp-ca.sh or your shell rc file only after the system bundle is correct.

curl documents CURL_CA_BUNDLE, SSL_CERT_FILE, and SSL_CERT_DIR as supported CA bundle options. Use them when a single tool still fails after the system store is updated:

bash
export CURL_CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt
output
(no output on success)

On Debian and Ubuntu, point CURL_CA_BUNDLE and SSL_CERT_FILE at /etc/ssl/certs/ca-certificates.crt instead.

Git

bash
git config --global http.sslCAInfo /etc/pki/tls/certs/ca-bundle.crt
output
(no output on success)

Git also honors GIT_SSL_CAINFO, which overrides http.sslCAInfo when set:

bash
export GIT_SSL_CAINFO=/etc/pki/tls/certs/ca-bundle.crt
output
(no output on success)

On Debian and Ubuntu, use /etc/ssl/certs/ca-certificates.crt for both settings.

Python and pip

From pip 24.2 onward, pip can use system certificates through truststore in addition to certifi. Older pip releases, virtual environments, requests-based tools, and custom Python apps may still need an explicit bundle path.

Check your pip and OpenSSL default paths:

bash
python3 -m pip --version
output
pip 23.3.2 from /usr/lib/python3.12/site-packages/pip (python 3.12)
bash
python3 -c "import ssl; print(ssl.get_default_verify_paths())"
output
DefaultVerifyPaths(cafile='/etc/pki/tls/cert.pem', capath='/etc/pki/tls/certs', openssl_cafile_env='SSL_CERT_FILE', openssl_cafile='/etc/pki/tls/cert.pem', openssl_capath_env='SSL_CERT_DIR', openssl_capath='/etc/pki/tls/certs')

On Debian and Ubuntu, cafile is typically /etc/ssl/certs/ca-certificates.crt.

For older pip, venvs, or apps that still fail:

bash
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
output
(no output on success)

You can also pass pip install --cert /path/to/bundle.pem for a single install session.

Node.js and npm

bash
export NODE_EXTRA_CA_CERTS=/etc/pki/tls/certs/ca-bundle.crt
output
(no output on success)

Use a PEM file containing one or more trusted CA certificates—not a leaf certificate or a single-site .cer export.

Or persist with npm config set cafile /path/to/bundle.pem.

Java

bash
keytool -importcert -alias corp-ca -file company-root-ca.crt -keystore "$JAVA_HOME/lib/security/cacerts" -storepass changeit -noprompt

On success, keytool reports that the certificate was added to the keystore.

Codex CLI

Codex login, HTTPS requests, and secure WebSockets can use a corporate CA bundle. Codex checks CODEX_CA_CERTIFICATE first; if it is unset, it falls back to SSL_CERT_FILE.

On RHEL-family systems:

bash
export SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt
export CODEX_CA_CERTIFICATE=/etc/pki/tls/certs/ca-bundle.crt
output
(no output on success)

On Debian-family systems, use /etc/ssl/certs/ca-certificates.crt for both variables.

bash
codex login --device-auth

When the corporate CA is trusted, Codex prints a device-auth URL instead of an SSL error. Add the exports to ~/.bashrc only after curl -v reports SSL certificate verify ok for an inspected site.


Docker, WSL, and containers

Container images ship their own /etc/ssl/certs tree. Installing the CA on Rocky or Ubuntu does not update an already-built Debian, Alpine, or Ubuntu container layer.

Options:

  1. Copy the corporate .crt into the image and run update-ca-certificates (Debian/Ubuntu) or update-ca-trust extract (RHEL-based images) during docker build.
  2. For Alpine-based images, install ca-certificates with apk and run update-ca-certificates inside the image:
dockerfile
RUN apk add --no-cache ca-certificates
COPY company-root-ca.crt /usr/local/share/ca-certificates/company-root-ca.crt
RUN update-ca-certificates
  1. Mount the host bundle read-only and set SSL_CERT_FILE or CURL_CA_BUNDLE in the container environment.
  2. For WSL, install the CA inside the WSL distro — the Windows certificate store is separate. New VirtualBox or VMware VMs also need the corporate CA installed on the guest OS even when NAT provides internet access.

If docker pull or docker build still fails after fixing the host, rebuild the image with the CA or pass NODE_EXTRA_CA_CERTS / REQUESTS_CA_BUNDLE into build stages that call npm or pip.


Why not to disable SSL verification

Forum threads often suggest bypass flags. They hide the trust problem and weaken every connection in that shell or config:

Shortcut Risk
curl -k / --insecure No server authentication for that request
git config --global http.sslVerify false All Git HTTPS remotes skip verification
pip install --trusted-host … Hostname allow-list, not full chain validation
npm config set strict-ssl false npm stops validating registry TLS
NODE_TLS_REJECT_UNAUTHORIZED=0 Disables TLS verification for Node processes

Use bypass flags only for a one-shot test to see whether TLS inspection is the blocker — then install the corporate CA properly. Only install CAs supplied by your security team or official IT documentation.


Troubleshooting checklist

Symptom Likely cause Fix
curl: (60) only on some hosts Selective SSL inspection Install corporate root CA; inspected sites will show vendor issuer in openssl s_client
curl works with proxy env set, fails with --noproxy '*' Forward proxy vs transparent inspection paths differ Diagnose with --noproxy '*'; align HTTP_PROXY and CA trust per IT guidance
update-ca-certificates reports does not contain a certificate DER file renamed to .crt Convert with openssl x509 -inform DER … (Step 3)
trust list shows CA but curl still fails Wrong cert (leaf/intermediate only) Export the root CA; ask IT for the forward-trust bundle
force-enable unknown command Old RHEL 7-era guide on modern Rocky/RHEL Place cert in /etc/pki/ca-trust/source/anchors/ and run update-ca-trust extract
Host fixed, container still fails Container CA bundle unchanged Add CA inside image or mount bundle; set SSL_CERT_FILE in container
verify error:num=10 certificate expired System clock wrong or expired leaf Run timedatectl; fix chrony or NTP sync before trusting new roots
Issuer is public CA but chain fails Server missing intermediate Server-side chain issue — not corporate inspection

References


Summary

curl: (60) SSL certificate problem: unable to get local issuer certificate on Linux usually means a corporate SSL inspection product is signing HTTPS traffic with a private root CA that is not in your distro trust store yet. Confirm with openssl s_client, install the corporate root CA on Debian (update-ca-certificates) or RHEL (update-ca-trust extract), verify with curl -v, then configure Git, Python, Node.js, Java, containers, or Codex to use the updated bundle. Avoid permanent -k or sslVerify false shortcuts — trust the corporate CA intentionally instead.


Frequently Asked Questions

1. What does curl error 60 unable to get local issuer certificate mean on Linux?

curl could not build a chain from the server certificate to a root CA in your local trust store. Behind corporate SSL inspection, the issuer is often your company proxy CA (for example Zscaler or Fortinet), not a public CA like DigiCert or Let's Encrypt.

2. Why does HTTPS work in my browser but curl or git fails on Linux?

Windows and managed browsers usually receive the corporate root CA through Group Policy or endpoint software. A Linux VM, WSL instance, container, or headless server starts with only public CAs until you install the inspection root CA with update-ca-certificates or update-ca-trust.

3. Should I use curl -k to fix SSL certificate problems permanently?

No. curl -k disables certificate verification for that request. Use it only for a one-off connectivity test. The durable fix is to install your organization's inspection root CA into the system trust store, then point language-specific tools at the updated bundle if they ignore the OS store.

4. Do I install the website certificate or the corporate root CA?

Install the corporate root CA (or the forward-trust CA your security team documents), not the short-lived leaf certificate for github.com or auth.openai.com. Leaf certs expire in days; the root CA is what signs all inspected traffic.

5. Where do I put a custom CA on Ubuntu versus Rocky Linux?

On Debian and Ubuntu, copy a PEM file to /usr/local/share/ca-certificates/ and run update-ca-certificates. On RHEL, Rocky, AlmaLinux, and Fedora, copy it to /etc/pki/ca-trust/source/anchors/ and run update-ca-trust extract.

6. Why do some HTTPS sites work while others fail on the same Linux host?

Many SSL inspection products inspect only selected URL categories. Sites that bypass inspection keep public CAs and verify fine; inspected sites present a proxy-signed certificate that fails until you trust the corporate CA.

7. Does installing the CA on the host fix Docker or WSL automatically?

No. Containers ship their own CA bundle. WSL does not inherit the Windows certificate store by default. Install or mount the corporate CA inside the container image or WSL distro, or configure tool-specific variables such as NODE_EXTRA_CA_CERTS and REQUESTS_CA_BUNDLE.

8. What is update-ca-trust force-enable on Rocky Linux?

Older RHEL 7-era guides may mention update-ca-trust force-enable. On Rocky Linux 10 this command returned unknown command in our test. On current RHEL-family systems, place the certificate under /etc/pki/ca-trust/source/anchors/ and run update-ca-trust extract.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …