APIs saying either 'unable to get local issuer certificate' or 'self-signed certificate in certificate chain'

I recently updated my certificates using certify the web, and after that I get errors trying to access APIs using PHP and curl.

Server is Windows 11/Apache 2.4.65/PHP 8.4.12.

Error messages are either ‘self-signed certificate in certificate chain’ or ‘unable to get local issuer certificate’, depending on which API I try to use.

Any Ideas?

This means your apache config is not pointing to the latest certificate files and instead they are pointing to the default self signed certificates that exist on your machine.

Certify Certificate Manager does not update your apache config, it just renews certificates and optionally exports those certificates as files you can use for specific apps (like Apache), if you configure it to do so.

You should review your apache config for ssl to see which files they point to.

Thank you for your response!

My httpd.conf file contains these lines:

SSLEngine                on
SSLCertificateFile       ./cert/lparker/certificate.crt
SSLCertificateKeyFile    ./cert/lparker/private.key
SSLCertificateChainFile  ./cert/lparker/ca_bundle.crt

which do point to the cert files that were installed automatically by the certify the web deployment. And, this did work before the latest cert renewal. I have not made any changes to the apache config. I have done renewals for this site before and not had a problem. There is a 4th file that was installed at the same time as the other three called cacert.pem, but I find no reference to it in the apache config file.

You should review your deployment task parameter configuration (for the Deploy to Apache to Deploy to Generic Server task) in Certify to ensure those are the files it is exporting.

I’ve deleted the cert files, then run CTW and confirmed they were recreated in the folder were Apache was looking for them. But I think I’ve been attacking this from the wrong end.

I changed the addresses of the APIs I’m using from https to http, and now two out of the three are working. The one the does not work gives the error message ‘unable to get local issuer certificate’.

It seems to be an issue with remote site certificate, and the fact I noticed it shortly after renewing my own certificate was a coincidence.

If you change to http then there will be no certificate used at all but a lot of things refuse to work over http nowadays and require https by default.

If this is a public API that is accessible of the internet you can use https://chainchecker.certifytheweb.com/ to get a quick summary of the certificate chain being presented by your API. There are other tools that can do the same things.

If working locally and you have access to openssl you can try
openssl s_client -showcerts -connect api.yourdomain.com:443 to see the certificates being served.

When you setup a certificate on a server you are pointing to teh certificate itself (the “leaf/end-entity” certificate) which covers the name of your service. That in turn is signed by an “intermediate” certificate from the CA, and that’s included in the bundle. The intermediate is in turn signed by the CAs root certificate.

Certificate trusts works by having the CAs latest root certificates in your systems local trust store, so for some clients that’s hte OS machine certificate store, and for others it’s a ca-bundle (list of trusted roots) installed by the app or library.

“unable to get local issuer certificate” means either the certificate being served is invalid or self-signed, or your client doesn’t know that root (needs an update).

Thanks again for your responses!

I ran this https://chainchecker.certifytheweb.com/ against my site, and against the three API sites I’m trying to use. My site and two of the API sites said ‘certificate chain ok’. For the other API site it said ‘This Let’s Encrypt chain uses the newer ISRG Root X1 root, which is trusted by current operating systems. This chain may cause issues for some old devices, particularly Android 7.1 and lower.’

I installed openssl and ran it against these same four sites. Three of them returned this message: Verify Error: num=20: unable to get local issuer certificate. The fourth one (which was the odd one above), returned this: ‘Verify Error 19: self-signed certificate in chain’.

Here are the openssl details from when I ran it against my own site:

CONNECTED(00000200)
depth=1 C=US, O=Let’s Encrypt, CN=R13
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN=(MyDomain)
verify return:1

Certificate chain
0 s:CN=(MyDomain)
i:C=US, O=Let’s Encrypt, CN=R13
a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption
v:NotBefore: Jan 29 19:06:14 2026 GMT; NotAfter: Apr 29 19:06:13 2026 GMT
-----BEGIN CERTIFICATE-----
(Details Omitted)
-----END CERTIFICATE-----
1 s:C=US, O=Let’s Encrypt, CN=R13
i:C=US, O=Internet Security Research Group, CN=ISRG Root X1
a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption
v:NotBefore: Mar 13 00:00:00 2024 GMT; NotAfter: Mar 12 23:59:59 2027 GMT
-----BEGIN CERTIFICATE-----
(Details Omitted)
-----END CERTIFICATE-----

Server certificate
subject=CN=(MyDomain)
issuer=C=US, O=Let’s Encrypt, CN=R13

No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: rsa_pss_rsae_sha256
Negotiated TLS1.3 group: X25519MLKEM768

SSL handshake has read 4220 bytes and written 1622 bytes
Verification error: unable to get local issuer certificate

New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Protocol: TLSv1.3
Server public key is 2048 bit
This TLS version forbids renegotiation.
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 20 (unable to get local issuer certificate)


Post-Handshake New Session Ticket arrived:
SSL-Session:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Session-ID: 9A9DB3DBF10775A5DF54E99F5225552B337C1BF0708E60EC482125EE374C2393
Session-ID-ctx:
Resumption PSK: 125E1F3E2D8BD8218A314A8CA1AB5E293AD8810BA17FA322CE36F993B1BF4FFF95D9216F62CF6917570780C858664EF5
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:
(Details Omitted)

Start Time: 1770834461
Timeout   : 7200 (sec)
Verify return code: 20 (unable to get local issuer certificate)
Extended master secret: no
Max Early Data: 0

read R BLOCK

Post-Handshake New Session Ticket arrived:
SSL-Session:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Session-ID: 0F1750DB4A611CE185EAC83DBA9F36C701785CFA3AA12EA14770F3372681D976
Session-ID-ctx:
Resumption PSK: 409FA9BB4BA4654743E052B6A269B168EA5E1E0F93AAA4B041804AB77849D42E0045DC988DCE493C8352A923C2FAAB33
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:

Start Time: 1770834461
Timeout   : 7200 (sec)
Verify return code: 20 (unable to get local issuer certificate)
Extended master secret: no
Max Early Data: 0

read R BLOCK
(Waits 60 Seconds, then types:)
closed

I really do not know where to go from here.