Skip to content
ecdsa.com
curl (OpenSSL backend)curl

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

How to fix it

  1. 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. 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. 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. 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

Browse the full signature error database