Bali, Indonesia
Insights

Why curl Rejects an SSL Certificate That Works in Your Browser

October 16, 2026
0:00
You run a quick curl command against your server or hit a newly deployed API endpoint, only to be stopped by an error: curl: (60) SSL certificate problem: unable to get local issuer certificate. Confused, you open the exact same address in Google Chrome, Firefox, or Safari, and the page loads cleanly with a valid security padlock. It is easy to assume the issue is a bug in curl, a broken local CA bundle on your workstation, or an aggressive corporate firewall. In almost every case where this happens, your server is misconfigured, but your browser is hiding the mistake from you. The web server is failing to send the complete certificate chain, and automated tools like curl are simply adhering strictly to standards where desktop browsers choose to be forgiving.
To understand this discrepancy, you have to look at how modern web browsers handle missing trust paths. When your web server accepts an incoming HTTPS connection, it presents a leaf certificate—the certificate issued specifically for your domain name. But your operating system does not know or trust your domain certificate directly; it only trusts a limited set of Root Certificate Authorities (Root CAs) installed in its local trust store. Between your leaf certificate and the Root CA sits at least one intermediate certificate. The intermediate certificate is what bridges the gap, creating a cryptographic chain: the Root signs the Intermediate, and the Intermediate signs your leaf. If your web server only serves the leaf certificate and omits the intermediate, a strict client cannot complete that chain of trust back to a trusted root. Desktop browsers work around this failure using two mechanisms: intermediate caching and Authority Information Access (AIA) chasing. If your browser has recently visited any other website that used the same intermediate certificate authority (such as a popular Let's Encrypt or Sectigo intermediate), the browser has already cached that intermediate certificate in memory. When it encounters your site, it silently plugs in the cached intermediate and marks the connection secure. Even if the intermediate is not in the browser's local cache, many modern browsers check the leaf certificate for an AIA extension. This field contains an HTTP URL pointing directly to the issuing authority's intermediate certificate. The browser pauses, downloads the missing intermediate in the background over plain HTTP, verifies the chain, and displays the padlock. You never see a warning, even though the server omitted required data.
Automated clients, command-line utilities, and API runtimes—including curl, Python's requests, Go's net/http, and Node.js—are built with different operational constraints than desktop web browsers. First, fetching missing certificates over the network introduces non-deterministic latency. In an automated script or a microservice architecture, an outbound HTTP call to download an intermediate certificate might block execution, trigger timeouts, or fail entirely if outbound network access is restricted by firewall egress rules. Second, following AIA pointers introduces complexity and potential attack surfaces. Chasing an unauthenticated HTTP link to fetch an intermediate certificate requires an extra network stack loop during what should be an isolated TLS handshake. If the remote server hosting the intermediate file is slow, down, or compromised, the client's handshake hangs or encounters protocol issues. Curl relies on underlying cryptographic libraries like OpenSSL, GnuTLS, or LibreSSL, which traditionally treat the TLS handshake as an offline verification process against the server's provided data and the local root store. Because curl does not maintain an opportunistic cache of certificates seen in previous sessions and does not perform AIA chasing by default, it evaluates only what your server presents during the handshake. If your server leaves out the intermediate link, curl fails immediately.
During the TLS handshake, after exchanging initial hello messages and negotiating cipher suites, the server sends a Certificate message to the client. RFC standards specify that the server should provide a list of certificates starting with the leaf and followed by any necessary intermediate certificates that lead to a root. When the intermediate is omitted, the client receives only the leaf certificate. The client inspects the leaf certificate's "Issuer" field, which names the intermediate certificate authority that signed it. The client then checks its local trust store to see if that issuer is a known, pre-installed root certificate. Because intermediate authorities are intentionally kept distinct from root stores for security and lifecycle management reasons, the lookup fails. The client cannot verify the digital signature on your leaf certificate because it does not possess the public key of the signer. Unable to link your certificate to a trusted root, the client aborts the connection to protect against potential man-in-the-middle attacks.
The clearest way to diagnose this issue without relying on browser heuristics is using the OpenSSL command-line client. By initiating a direct connection to your server and requesting the certificate chain, you can see exactly what certificates your web server sends and where verification halts. You can run the following diagnostic command against your domain on port 443:
BASH
# Check the certificates sent by the server during the TLS handshake
openssl s_client -connect example.com:443 -servername example.com
When you run this against a misconfigured server, look closely at the Certificate chain and Verify return code sections in the output. A broken setup usually produces an output structure like this:
TEXT
--- 
Certificate chain
 0 s:CN = example.com
   i:C = US, O = Let's Encrypt, CN = R3
--- 
...
Verify return code: 21 (unable to verify the first certificate)
Notice that the certificate chain contains only entry 0, which is the leaf certificate (example.com). The issuer is listed as R3, but the server never sent the R3 intermediate certificate. Because the local trust store does not contain R3 as an anchor (it contains the parent root, such as ISRG Root X1), OpenSSL terminates the handshake with return code 21: unable to verify the first certificate. On a correctly configured web server, the output displays multiple entries in the chain: entry 0 for the leaf certificate and entry 1 (or more) for the intermediate certificates. When the chain ends on a certificate signed by a root present in your local trust store, the final line reads Verify return code: 0 (ok).
Fixing a broken certificate chain requires updating your web server configuration so that it serves the full chain—often distributed as a bundle or full-chain file—rather than just the primary domain certificate. When you obtain an SSL/TLS certificate, whether through an automated ACME client or a commercial provider, you are typically provided with several files:
  1. The private key (privkey.pem or domain.key).
  2. The leaf certificate (cert.pem or domain.crt).
  3. The intermediate bundle (chain.pem or ca-bundle.crt).
  4. A combined full-chain file (fullchain.pem), which contains the leaf certificate followed directly by the intermediate certificate(s).
The order inside the combined file matters. The leaf certificate must always come first, followed by the intermediate certificates that bridge the path to the root. You should generally avoid including the Root CA certificate itself in this bundle, as clients already store root certificates locally; sending the root wastes bandwidth during the TLS handshake. In Nginx, the ssl_certificate directive must point to the combined file containing both the leaf and the intermediate:
TEXT
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
If you point ssl_certificate to cert.pem instead of fullchain.pem, Nginx will only serve the leaf, causing the exact curl failure described here. In Apache HTTP Server, the configuration depends on your Apache version. On Apache 2.4.8 and newer, the configuration mirrors Nginx: SSLCertificateFile should point to the full-chain file containing both your leaf and intermediate certificates. On older Apache versions, intermediate certificates had to be defined separately using the SSLCertificateChainFile directive, while SSLCertificateFile held only the domain certificate. Once you update the configuration files and reload your web server, rerun the openssl s_client command or test with curl -vI https://example.com. Once the complete chain is served, curl will establish the connection without warnings. Testing your sites with curl or OpenSSL should be a standard part of deploying any new certificate. A desktop browser's ability to render your site smoothly does not guarantee that your certificate deployment conforms to TLS specifications, and API clients or automated background workers will quickly expose an incomplete chain.