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.
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.
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).
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
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)
Start Time: 1770834461
Timeout : 7200 (sec)
Verify return code: 20 (unable to get local issuer certificate)
Extended master secret: no
Max Early Data: 0
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
Can you double check that your deployment task to export the certificate is exporting the “full chain” certificate file and that your apache config is pointing to that file for the certificate.
If you are using the full chain certiicate file you shouldn’t need the line that says
Output filepath for cert - d:\Apache24\cert\lparker\cacert.pem
Output filepath for key - d:\Apache24\cert\lparker\private.key
Output filepath for full chain - d:\Apache24\cert\lparker\certificate.crt
Output filepath for CA chain - d:\Apache24\cert\lparker\ca_bundle.crt
The httpd.conf file contains these lines:
SSLEngine on
SSLCertificateFile ./cert/lparker/certificate.crt
SSLCertificateKeyFile ./cert/lparker/private.key
SSLCertificateChainFile ./cert/lparker/ca_bundle.crt
I tried commenting out that last one but it did not make a difference that I could see.
Thanks, your certificate is fine. The problem is with whatever is making the request having an outdated or incomplete ca bundle (list of trusted roots) and not knowing ISRG Root X1.
I’ve been using PhP’s curl function. And I found a fix:
Adding the line:
CURLOPT_SSL_VERIFYPEER => false,
in the PhP curl setup has made all the problems disappear. I’m not sure if it is actually ‘fixed’ or if I’m just covering up some unknown problem, but the net result is everything works even after changing the links back to https: instead of http:
Using this option is essentially the same as telling your web browser to continue anyways when it says the validity of the site could not be verified. The remote site could be using a self-signed certificate and the client won’t care. The connection remains encrypted, but could be MitM attacked.
You should instead update the OS’s CA root store and make sure cURL is new enough and configured to use it -or- point cURL to an updated file you download yourself.