Error message
curl: (60) SSL certificate problem: self-signed certificate
Also printed as
- curl: (60) SSL certificate problem: self signed certificate
- curl: (60) SSL certificate OpenSSL verify result: self-signed certificate (18)
OpenSSL 3.0 and later print "self-signed certificate"; OpenSSL 1.1.1 prints "self signed certificate". Newer curl releases (8.22.0 in our test) word it "SSL certificate OpenSSL verify result: self-signed certificate (18)". Do not confuse it with "self-signed certificate in certificate chain", where a CA chain exists and ends in an untrusted root.
curl (OpenSSL backend) — CURLE_PEER_FAILED_VERIFICATION, X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT (18)
What it means
The server presented a single certificate that is signed by its own key (issuer equals subject), and curl does not have that exact certificate in its trust store. There is no CA behind it that curl could look up, so verification stops at depth 0. This is normal for development servers and appliances, and it is never fixed by installing an intermediate.
Why it happens
A development or test server with a generated certificate
commonLocal HTTPS servers, docker-compose stacks and test fixtures often generate a self-signed certificate at start-up (for example with openssl req -x509). Each regeneration produces a new certificate, so a copy trusted yesterday may not match today.
A device or product still uses its factory certificate
commonRouters, NAS boxes, printers, iDRAC/iLO interfaces, Proxmox, Jenkins or Elasticsearch installs ship with a self-signed certificate until someone installs a proper one.
A proxy or ingress serves its default certificate
occasionalWhen no certificate matches the requested name, ingress-nginx serves "Kubernetes Ingress Controller Fake Certificate" and Traefik serves "TRAEFIK DEFAULT CERT", both self-signed. The real problem is then routing or SNI, not trust.
The server points at the wrong file
rareA configuration that references a temporary self-signed certificate (snakeoil.pem on Debian, localhost.crt from a package) instead of the issued one produces this error even though a real certificate exists on disk.
How to fix it
- 1.
Trust exactly this certificate, after checking it
For a single internal endpoint, save the certificate, confirm its SHA-256 fingerprint through a trusted channel (the device's console, the admin who created it), and pass it with --cacert. curl then accepts this certificate and nothing else for that call.
bash # Fetch the certificate once, compare its fingerprint out of band, then trust exactly that file openssl s_client -connect api.example.com:8443 -servername api.example.com </dev/null 2>/dev/null \ | openssl x509 -out api.example.com.pem openssl x509 -in api.example.com.pem -noout -fingerprint -sha256 curl --cacert api.example.com.pem https://api.example.com:8443/ - 2.
Development: issue certificates from a local CA
A local CA (mkcert, step-ca, or a hand-made root) lets you trust one root once and issue certificates for any number of dev hosts. The certificates are no longer self-signed, so the error disappears everywhere the root is installed.
bash # mkcert creates a local CA, installs it into the system and browser stores, # and issues certificates signed by it (no longer self-signed) mkcert -install mkcert api.example.test localhost 127.0.0.1 - 3.
Public hosts: install a certificate from a public CA
Anything reached by other people or by third-party clients should present a certificate from a publicly trusted CA; ACME clients such as certbot, lego or Caddy obtain and renew one automatically.
bash # Public names: get a certificate from a public CA, e.g. with certbot (ACME) sudo certbot certonly --nginx -d api.example.com - 4.
Temporary diagnostic: check the service with verification off
-k makes curl skip certificate checks, so a 200 confirms that only trust is missing. Do not keep it: a self-signed certificate accepted without checking can be swapped for an attacker's at any time and curl would not notice.
bash # Diagnostic only: confirms the service answers when certificate checks are skipped curl -sS -k -o /dev/null -w '%{http_code}\n' https://api.example.com:8443/
Related errors
curl: (60) SSL certificate problem: self-signed certificate in certificate chaincurl (OpenSSL backend)curl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)verify error:num=18:self-signed certificateOpenSSL CLI (openssl s_client / openssl verify)code: 'DEPTH_ZERO_SELF_SIGNED_CERT'Node.js tls / https / fetch