Overview
SSL certificate and proxy issues are among the most common problems developers and security professionals face — particularly when moving between home and corporate environments. This guide provides a systematic troubleshooting approach.
Categorising the Error
SSL errors fall into several categories:
1. Certificate Trust Errors
CERTIFICATE_VERIFY_FAILED
unable to get local issuer certificate
self-signed certificate in certificate chain
SSL_ERROR_BAD_CERT_DOMAIN
Cause: The certificate’s issuer (CA) is not trusted by the client.
2. Certificate Validity Errors
certificate has expired or is not yet valid
CERT_NOT_YET_VALID / CERT_DATE_INVALID
Cause: The certificate’s validity period is outside the current date/time.
3. Hostname Mismatch Errors
SSL: CERTIFICATE_VERIFY_FAILED
hostname 'api.example.com' doesn't match certificate's CN 'example.com'
Cause: The hostname doesn’t match the Subject Alternative Names (SANs) in the certificate.
4. Proxy-Related Errors
407 Proxy Authentication Required
CONNECT tunnel failed with status 407
Could not resolve proxy hostname
Cause: Proxy configuration missing or proxy requires authentication.
The Diagnostic Flowchart
SSL Error Encountered
│
▼
Is this a corporate environment?
│ │
YES NO
│ │
▼ ▼
Check if proxy Check cert expiry
is intercepting and hostname match
│ │
▼ ▼
openssl s_client openssl x509 -text
check issuer check dates and SANs
Step-by-Step Diagnostics
Check 1: Examine the Certificate
# Check what certificate a server presents
echo | openssl s_client -connect hostname:443 2>/dev/null | openssl x509 -noout -text
# Quick summary
echo | openssl s_client -connect hostname:443 2>/dev/null | openssl x509 -noout \
-subject -issuer -dates -extensions
# Check Subject Alternative Names
echo | openssl s_client -connect hostname:443 2>/dev/null | \
openssl x509 -noout -text | grep -A1 "Subject Alternative"
Check 2: Verify the Chain
# Verify against system trust store
openssl s_client -connect hostname:443 -verify_return_error 2>&1 | grep -E "verify|Verify"
# Show full chain
openssl s_client -connect hostname:443 -showcerts 2>/dev/null | \
grep -E "BEGIN CERTIFICATE|subject|issuer"
Check 3: Test from Different Contexts
# Test with curl (uses system CA)
curl -v https://hostname 2>&1 | grep -E "SSL|TLS|error|OK"
# Test with Python requests
python3 -c "import requests; print(requests.get('https://hostname').status_code)"
# Test with Node.js
node -e "const https=require('https'); https.get('https://hostname',r=>console.log(r.statusCode))"
# Test bypassing verification (DIAGNOSTIC ONLY - never in production)
curl -k https://hostname
If curl -k succeeds but curl fails, it’s a trust issue.
Common Fixes
Fix 1: Untrusted CA (Corporate Proxy)
# Find the corporate CA
echo | openssl s_client -connect api.service.com:443 2>/dev/null | \
openssl x509 -outform PEM > /tmp/proxy-ca.pem
# Verify it's the CA (not the leaf cert)
openssl x509 -in /tmp/proxy-ca.pem -text -noout | grep -E "CA:|Basic Constraints"
# Should show: CA:TRUE
# Trust it system-wide (Ubuntu)
sudo cp /tmp/proxy-ca.pem /usr/local/share/ca-certificates/proxy-ca.crt
sudo update-ca-certificates
# Trust it system-wide (RHEL/CentOS)
sudo cp /tmp/proxy-ca.pem /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust extract
# Trust it for specific tools
export REQUESTS_CA_BUNDLE=/tmp/proxy-ca.pem # Python requests
export NODE_EXTRA_CA_CERTS=/tmp/proxy-ca.pem # Node.js
export SSL_CERT_FILE=/tmp/proxy-ca.pem # curl + OpenSSL
git config --global http.sslCAInfo /tmp/proxy-ca.pem # git
Fix 2: Expired Certificate
If you control the certificate:
# Check expiry
openssl x509 -in cert.pem -noout -dates
# Renew with Let's Encrypt
certbot renew
# Or use acme.sh
acme.sh --renew -d yourdomain.com
If you don’t control the certificate (external service):
- Contact the service provider
- Check for a status page or known incident
Fix 3: Hostname Mismatch
# Check what names the cert covers
echo | openssl s_client -connect hostname:443 2>/dev/null | \
openssl x509 -noout -text | grep -A3 "Subject Alternative"
# Typical output:
# X509v3 Subject Alternative Names:
# DNS:example.com, DNS:*.example.com, DNS:www.example.com
If the hostname doesn’t match:
- Use a hostname that IS in the SANs
- Request a new certificate with the correct SANs included
Fix 4: Proxy Configuration Issues
# Test proxy connectivity
curl -x http://proxy:port https://httpbin.org/ip
# Test with authentication
curl -x http://user:password@proxy:port https://httpbin.org/ip
# Check if CONNECT method works (required for HTTPS)
curl -v -x http://proxy:port https://example.com 2>&1 | grep -E "CONNECT|Establish"
# NO_PROXY - bypass for internal addresses
export NO_PROXY="localhost,127.0.0.1,10.0.0.0/8,192.168.0.0/16,.internal.com"
Tool-Specific Proxy and SSL Configuration
Python (requests / pip)
# For pip
pip install package --trusted-host pypi.org --trusted-host files.pythonhosted.org
# Or:
pip install package --proxy http://proxy:port
pip config set global.proxy http://proxy:port
# For requests in code
import requests
session = requests.Session()
session.verify = '/path/to/ca-bundle.pem'
session.proxies = {'https': 'http://proxy:port'}
npm / Node.js
npm config set proxy http://proxy:port
npm config set https-proxy http://proxy:port
npm config set cafile /path/to/ca.pem
# Or per-command
npm install --proxy http://proxy:port
git
git config --global http.proxy http://proxy:port
git config --global http.sslCAInfo /path/to/ca.pem
# Disable SSL verify for a single repo (testing only!)
git config http.sslVerify false
curl (persistent)
# ~/.curlrc
proxy = http://proxy:port
cacert = /path/to/ca.pem
Docker
# Dockerfile
ENV http_proxy=http://proxy:port \
https_proxy=http://proxy:port
# Or at build time
docker build --build-arg HTTPS_PROXY=http://proxy:port .
When to Escalate
Contact your network/security team when:
- Proxy auth keeps expiring — you may need certificate-based auth or an exception
- Specific tools or protocols are blocked — modern tools (gRPC, WebSocket, HTTP/3) may need specific proxy support
- SSL inspection breaks pinned apps — mobile or enterprise apps with certificate pinning need inspection bypasses
- Compliance requirements — some tools must not be inspected; request an exemption with justification
Security Warning
Never disable SSL verification in production code. Common antipatterns:
# DANGEROUS - never in production
requests.get(url, verify=False)
urllib3.disable_warnings()
// DANGEROUS - never in production
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
These completely disable the authentication purpose of TLS — anyone can intercept your connection. The correct fix is always to add the required CA to your trust store.