Fix "SSL certificate problem: self-signed certificate" Error in curl and HTTP Clients

beginner๐Ÿ”’ SSL/TLS2026-03-29| Linux, macOS, Windows โ€” curl, Python requests, Node.js, Git, wget, any HTTPS client connecting to a server using a self-signed TLS certificate

Error Message

SSL certificate problem: self-signed certificate
#ssl#self-signed#certificate#curl

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: -k flag and move on
  • Same server repeatedly: add the cert to your system trust store once
  • Python scripts: verify='/path/to/cert.pem' instead of verify=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

Related Error Notes