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

Error message

curl: (60) SSL certificate problem: certificate has expired

Also printed as

  • curl: (60) SSL certificate OpenSSL verify result: certificate has expired (10)

The reason text is identical in OpenSSL 1.1.1, 3.0 and 3.5 builds of curl. Newer curl releases (8.22.0 in our test) word it "SSL certificate OpenSSL verify result: certificate has expired (10)". Certificates that are not valid yet produce a different reason: "certificate is not yet valid".

curl (OpenSSL backend) — CURLE_PEER_FAILED_VERIFICATION, X509_V_ERR_CERT_HAS_EXPIRED (10)

What it means

One certificate in the chain curl built has a notAfter date earlier than the current time on the machine running curl. It is usually the server's own certificate, but an expired intermediate or cross-signed root produces the same text. The check is done against the local clock, so a wrong clock looks exactly like an expired certificate.

Why it happens

How to fix it

  1. 1.

    Find which certificate expired

    Print the notAfter date of every certificate the server sends. If the leaf is expired, renew it; if an intermediate is, replace the chain file with the one your CA currently publishes.

    bash
    # Split the served chain into cert1.pem (leaf), cert2.pem, ... and print each expiry date
    openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \
      | awk '/BEGIN CERT/{n++; f="cert" n ".pem"} /BEGIN CERT/,/END CERT/{print > f}'
    for f in cert*.pem; do openssl x509 -in "$f" -noout -subject -enddate; done
  2. 2.

    Renew, reload, and check what is actually served

    After renewing, reload every process and device that terminates TLS for the name, then confirm the date from the network side rather than from the file on disk.

    bash
    sudo certbot renew                    # or your ACME client / CA portal
    sudo systemctl reload nginx           # the server keeps serving the old file until reloaded
    echo | openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null \
      | openssl x509 -noout -enddate      # confirm the new date is what the network sees
  3. 3.

    Correct the client's clock

    When the certificate is fine for other machines, synchronise the failing machine's time with NTP. In containers the clock comes from the host, so fix it there.

    bash
    date -u                       # compare with the real UTC time
    timedatectl status            # "System clock synchronized: yes"?
    sudo timedatectl set-ntp true
  4. 4.

    Update the client's CA bundle

    On long-lived systems, an updated ca-certificates package removes retired roots and adds current ones, which lets OpenSSL pick a valid path instead of an expired one.

    bash
    # Old clients: refresh the root store so they stop building paths through retired roots
    sudo apt-get update && sudo apt-get install --only-upgrade ca-certificates   # Debian/Ubuntu
    sudo dnf upgrade ca-certificates                                             # RHEL/Fedora

Related errors

Browse the full signature error database