The Error
You hit an HTTPS endpoint and get this back:
curl: (60) SSL certificate problem: self-signed certificate
More details here: https://curl.se/docs/sslcerts.html
curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it.
The server signed its own TLS certificate instead of getting one from a trusted Certificate Authority (CA). Your HTTP client sees no recognized chain of trust and refuses to connect. That's it.
Why This Happens
Every TLS client ships with a list of trusted root CAs โ think Let's Encrypt, DigiCert, GlobalSign. A self-signed cert doesn't trace back to any of them, so the handshake fails. You'll run into this in a few common places:
- Internal dev or staging servers where nobody bothered with a real cert
- Docker containers, local Kubernetes clusters (kind, minikube), or private APIs
- Corporate proxies that intercept HTTPS traffic and re-sign it with their own cert
- Legacy servers still running a cert that expired years ago and got self-renewed
Fix 1: Add the Self-Signed Certificate to Your Trust Store (Proper Fix)
For anything you access more than once, this is the move. You trust the specific cert without nuking SSL verification everywhere.
Step 1: Pull the certificate from the server
# Save the server's certificate to a file
openssl s_client -connect your-server.example.com:443 -showcerts </dev/null 2>/dev/null \
| openssl x509 -outform PEM > server-cert.pem
Step 2: Use it in curl
curl --cacert server-cert.pem https://your-server.example.com/api/endpoint
Step 3: Add to the system trust store
Do this once and every tool on the machine will trust it automatically.
On Ubuntu/Debian:
sudo cp server-cert.pem /usr/local/share/ca-certificates/my-server.crt
sudo update-ca-certificates
On RHEL/CentOS/Fedora:
sudo cp server-cert.pem /etc/pki/ca-trust/source/anchors/my-server.crt
sudo update-ca-trust
On macOS:
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain server-cert.pem
On Windows (PowerShell as Admin):
Import-Certificate -FilePath .\server-cert.pem -CertStoreLocation Cert:\LocalMachine\Root
Fix 2: Skip SSL Verification (Quick Fix โ Dev Only)
Fast and dirty. Fine for localhost or a trusted internal network. If you ship this to production, someone will eventually have a bad day.
curl
curl -k https://your-server.example.com/api/endpoint
# or the long form
curl --insecure https://your-server.example.com/api/endpoint
Python (requests library)
import requests
# Skip verification โ dev only, suppresses the InsecureRequestWarning too:
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
response = requests.get('https://your-server.example.com/api', verify=False)
# Better option: point directly to the cert file
response = requests.get('https://your-server.example.com/api', verify='/path/to/server-cert.pem')
Node.js (https module)
const https = require('https');
const fs = require('fs');
// Option 1: skip verification entirely (dev only)
// Set before any HTTPS calls
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
// Option 2: provide the cert via a custom agent (works with node-fetch or axios)
const agent = new https.Agent({
ca: fs.readFileSync('/path/to/server-cert.pem')
});
// With axios:
const axios = require('axios');
axios.get('https://your-server.example.com/api', { httpsAgent: agent });
// With node-fetch:
const fetch = require('node-fetch');
fetch('https://your-server.example.com/api', { agent });
Git
# One-time clone with SSL skipped
git clone -c http.sslVerify=false https://your-server.example.com/repo.git
# Point Git to your cert instead (safer)
git config --global http.sslCAInfo /path/to/server-cert.pem
# Global disable โ works but leaves you exposed everywhere
git config --global http.sslVerify false
wget
wget --no-check-certificate https://your-server.example.com/file.zip
Fix 3: Replace the Self-Signed Cert with a Real One
If you own the server, just get a proper certificate. For internal services without public HTTP access, Let's Encrypt's DNS challenge works perfectly โ no need to expose port 80:
# DNS challenge โ no public HTTP required
certbot certonly --manual --preferred-challenges dns -d your-internal-server.example.com
Running a fully air-gapped environment? Stand up an internal CA using step-ca, then push its root cert to all machines via Ansible, Group Policy, or your config management tool of choice. Every service inside your network gets a legit cert, zero self-signed hassle.
Verify the Fix
# Clean response, no SSL errors
curl -v https://your-server.example.com/
# Inspect the certificate details
openssl s_client -connect your-server.example.com:443 </dev/null | openssl x509 -noout -text
# Confirm the cert chain resolves correctly
curl --cacert server-cert.pem -v https://your-server.example.com/ 2>&1 | grep -E "SSL|certificate|issuer"
A working connection prints SSL connection using TLSv1.3 (or TLSv1.2) in verbose output with no certificate warnings.
Quick Reference
- One-off curl request:
-kflag and move on - Same server repeatedly: add the cert to your system trust store once
- Python scripts:
verify='/path/to/cert.pem'instead ofverify=False - Production systems: replace the self-signed cert โ no exceptions
- Corporate proxy blocking you: ask IT for the proxy CA cert and add it to your trust store

