2026年11月12日

2026年11月12日

Fix SSL Intermediate CA (Chain) Errors: Checks and Nginx/Apache Setup

This article is a localized edition of the Japanese original. Read the Japanese original

If your HTTPS site opens fine in a desktop browser but connections from mobile apps, curl, an external service's webhook or WordPress on another server fail with "unable to verify the certificate," the cause is usually that the server isn't sending the intermediate CA certificate. This article covers how to check whether it's missing, how to build a correct chain, how to configure Nginx, Apache and Certbot, and how to confirm the fix.

How intermediate CA certificates and chains work

Server certificates are usually issued by an intermediate CA rather than directly by a root CA. A connecting client trusts the certificate by following this sequence (the chain):

  1. Server certificate (the certificate for your domain)
  2. Intermediate CA certificate (the CA that issued 1; there may be more than one level)
  3. Root CA certificate (preinstalled in the OS or browser)

The server needs to send 1 and 2. The client uses its own copy of the root certificate in 3, so you don't need to send it. If the intermediate is missing, the client can't connect 1 to 3 and verification fails.

Desktop browsers rarely show the problem because some of them fetch missing intermediates automatically or reuse intermediates they've seen before. curl, PHP and apps often don't do this, which is where the error finally surfaces.

Step 1: Check whether the intermediate certificate is missing

Use openssl s_client to see which certificates the server actually sends. Without -servername, a server hosting several domains may return a different certificate.

openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null

Look at two places in the output:

  • The Certificate chain list: 0 s: should be your domain, with intermediate CAs from 1 s: onward. If there's only 0, no intermediate is being sent
  • The final Verify return code: 0 (ok) means everything is fine. 21 (unable to verify the first certificate) and 20 (unable to get local issuer certificate) are typical when the intermediate is missing

You can confirm the same problem with curl.

curl -sSI https://example.com/
# If the intermediate certificate is missing, you get an error like this
# curl: (60) SSL certificate problem: unable to get local issuer certificate

The WordPress HTTP API (wp_remote_get() and friends) also uses cURL internally, so fetching from or integrating with other sites can produce the same cURL error 60.

Step 2: Inspect the certificate file you have

Check what's in the file you're configuring. Even when one file holds several certificates, this command shows every certificate's subject (whose certificate it is) and issuer (who issued it).

# Number of certificates in the file
grep -c 'BEGIN CERTIFICATE' fullchain.pem

# Show the subject and issuer of every certificate in order
openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -noout

A correct file meets these conditions:

  • The first certificate is for your domain
  • Each certificate's issuer matches the next certificate's subject
  • It doesn't include a certificate whose subject and issuer are the same (a self-signed root)

A certificate whose subject and issuer differ is a "cross-signed" intermediate signed by another root, even if its name contains "Root." As of October 2026, Let's Encrypt uses chains in which the server certificate is followed by an intermediate CA and such cross-signed certificates. They are required intermediates, so don't remove them.

Step 3: Get the intermediate certificate

Intermediate certificates are distributed by the CA that issued your certificate (or the reseller). They're often included in the issuance email, the ZIP file you downloaded, or the "intermediate certificate" field of the control panel, so use that first.

If you can't find it, the server certificate may state where to get its issuer's certificate (CA Issuers).

# Check the CA Issuers URL in the server certificate's AIA extension (OpenSSL 1.1.1 or later)
openssl x509 -in server.crt -noout -ext authorityInfoAccess

# Download from the CA Issuers URL shown (replace the URL with the one from the output above)
curl -o intermediate.der http://ca.example.net/intermediate.crt

# If the downloaded file is DER, convert it to PEM
openssl x509 -inform DER -in intermediate.der -out intermediate.pem

Before using it, confirm that the downloaded file's subject matches the server certificate's issuer.

Step 4: Build the fullchain in the right order and verify it

Put the server certificate first, followed by the intermediate certificate. Don't include the root certificate.

# Concatenate in order: server certificate, then intermediate
cat server.crt intermediate.pem > fullchain.pem

# If the first file lacks a trailing newline, the boundary between certificates breaks. Make sure no line like this exists
grep -n 'END CERTIFICATE-----.*BEGIN' fullchain.pem

Before deploying, verify that the chain connects. If you see OK, it correctly reaches a root certificate held by the OS.

openssl verify -untrusted intermediate.pem server.crt
# server.crt: OK

Also confirm that the private key and server certificate are a pair. If the two outputs match, they belong together.

openssl x509 -in server.crt -noout -pubkey | openssl sha256
openssl pkey -in privkey.pem -pubout | openssl sha256

Make the private key file readable only by its owner (chmod 600 privkey.pem).

Step 5: Configure the web server

Nginx

Point ssl_certificate at the fullchain that combines the server certificate and the intermediate.

server {
    listen 443 ssl;
    server_name example.com;

    # Server certificate + intermediate (one file, in this order)
    ssl_certificate     /etc/ssl/example.com/fullchain.pem;
    ssl_certificate_key /etc/ssl/example.com/privkey.pem;
}

ssl_trusted_certificate specifies certificates used to verify OCSP responses and client certificates, and the certificates listed there are not sent to clients. It won't fix a missing intermediate, so fix the file in ssl_certificate instead.

sudo nginx -t
sudo systemctl reload nginx

Apache (2.4.8 or later)

From Apache 2.4.8, SSLCertificateFile can point to a file that includes the intermediate certificates. The previously used SSLCertificateChainFile became obsolete in 2.4.8.

<VirtualHost *:443>
    ServerName example.com
    SSLEngine on
    SSLCertificateFile    /etc/ssl/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/ssl/example.com/privkey.pem
</VirtualHost>
sudo apachectl configtest
# Ubuntu / Debian
sudo systemctl reload apache2
# RHEL / Rocky Linux / AlmaLinux
sudo systemctl reload httpd

With Let’s Encrypt (Certbot)

Certbot creates these files in /etc/letsencrypt/live/<domain>/.

File Contents
cert.pem Server certificate only
chain.pem Intermediates only
fullchain.pem Server certificate + intermediates
privkey.pem Private key

Use fullchain.pem as the web server's certificate. Pointing it at cert.pem leaves out the intermediate. Let's Encrypt's intermediates change from time to time, and as of October 2026 the official page lists the old "Let's Encrypt Authority X3" as expired. Rather than downloading an intermediate from an X3 URL in an old article and concatenating it, use the fullchain.pem that Certbot regenerates on every renewal as is.

Shared hosting, CDNs and load balancers

If the control panel has separate fields for "certificate," "private key" and "intermediate certificate (CA certificate)," paste the intermediate CA certificate into the intermediate field. Don't include the root. If HTTPS terminates at a CDN or load balancer, fix the certificate settings on that service rather than on the web server.

Step 6: Confirm after applying

After applying the change, run the Step 1 command again and confirm that intermediate CAs appear in Certificate chain and that you get Verify return code: 0 (ok). For an outside check, you can also use Qualys SSL Server Test (SSL Labs). In the results, "Chain issues: Incomplete" means an intermediate is missing, and "Contains anchor" means you've included the root certificate.

When you renew a certificate, rebuild the chain with the intermediate supplied with the new certificate. Even with the same CA, the issuing intermediate can change at renewal.

Summary

  • If a site opens in desktop browsers but fails in curl, apps or webhooks, suspect a missing intermediate certificate
  • Use openssl s_client -servername to check which certificates are sent and the Verify return code
  • The chain goes "server certificate → intermediate." Don't include the root
  • Point Nginx's ssl_certificate or, on Apache 2.4.8+, SSLCertificateFile at the fullchain. With Certbot, use fullchain.pem

Related articles:

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲