Openssl verify fails with E1

My domain is:

I ran this command: openssl verify /etc/letsencrypt/live/

It produced this output:
CN =
error 20 at 0 depth lookup: unable to get local issuer certificate
error /etc/letsencrypt/live/ verification failed

The operating system my web server runs on is (include version): raspberry os bullseye
The version of my client is (e.g. output of certbot --version or certbot-auto --version if you're using Certbot): certbot 1.22.0

The web stack works fine with this configuration. However, the openssl verify fails. Since I'm trying to run a TLS application (syncplay) which uses libopenssl, it will fail too.
Please note that I'm on the "beta" E1 Intermediate Certificate. Maybe that's why?
The system is updated, I also tried to run update-ca-certificates
OpenSSL 1.1.1k 25 Mar 2021

Thank you for any answer :slight_smile:

1 Like

End leaf certificates are signed by the intermediate (chain.pem). Those intermediates are not included in root stores for obvious reasons. You should verify cert.pem against chain.pem.

Notice that, depending on the chain, OpenSSL might complain about the expired "DST Root CA X3" root certificate, as that's trust anchor of the current default chain. This is to be expected.


That's not the correct invocation of openssl for this purpose.

for the correct args and usage, see:


Hi @ElDavo and welcome to the LE community forum :slight_smile:


Have you tried it?


thank you :slight_smile:

I.... don't know :sweat_smile: . From the other comments looks like i used the wrong command.
I want to understand why syncplay (the application) fails to verify my certificate, i've read the code and looks like it just tries to connect using the twisted python library (which in turn automatically uses libopenssl), so no custom stuff, for this reason I tried to use openssl to do the same verification the application does. And I failed lol

Yep, that's why I'm here.

For context, the "raw" output of the app is:
[('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')]

1 Like

Does the same happen with R1 (if you want to do tries, use Apricot -- don't forget to add its root)?

You should ask openssl to tell you the chain.

openssl s_client -connect address:port -servername domainname -verify 10

I can't try myself because you're serving a cloudflare certificate.

1 Like

You may be complicating things by having your syncplay server running behind Cloudflare. With Cloudflare, usually the client app uses https to connect to the Cloudflare edge and the edge makes another https connection back to the origin server. If that's how you have it then the syncplay client will see the Cloudflare cert - not yours (see below).

Ignoring that for now ... I saw this in the syncplay instructions. Did you see this note in the syncplay docs on github?

NOTE: please ensure that your certificates are valid for your domain name. If the client is unable to verify the certificates, it will prevent the connection entirely. In this case, there will be no attempt to establish an unencrypted connection, imitating the behavior of modern browsers. We use the certifi store (link) to validate certificates. If you experience troubles in making clients validate your certificates, we encourage you to disable TLS on your server and privately test them before enabling this feature again

Note: the certifi store link in the github text results in 403 error.

openssl s_client -connect -servername

Certificate chain
 0 s:/C=US/ST=California/L=San Francisco/O=Cloudflare, Inc./
   i:/C=US/O=Cloudflare, Inc./CN=Cloudflare Inc ECC CA-3
 1 s:/C=US/O=Cloudflare, Inc./CN=Cloudflare Inc ECC CA-3
   i:/C=IE/O=Baltimore/OU=CyberTrust/CN=Baltimore CyberTrust Root

Okay, a Python crypto error is totally different than the openssl invocation!

As a user of Python (and Twisted), I have multiple versions of openssl and Python installed on my machines, and multiple virtualenvs referencing each one. Often times, the (first) openssl version in a user's path is not the same one that Python is relying on.

If you're familiar enough with Python, try adding some lines into it to print/debug which libopenssl it's using.

You can also put together a quick little script to load the certs and verify within Python. You can base it on this bit:


And unfortunately I can't do that becase the application uses "opportunistic tls", which means that before the handshake the client asks the server for tls support in an unencrypted way and the server answers "yes" and only after this the encrypted handshake happens.
Unless I make a python hack of some sort?

No, the application is not proxied through Cloudflare. I'm using a (secret) non-proxied subdomain, but since the certificate is wildcard this should not be a problem. (I also tried with my backup/old domain that isn't proxied and does not have a wildcard certificate).

Yeah this app does not have a very active development

1 Like

You might want to try using a cert from a different Certificate Authority. After Sept30 2021 there were many, um, unusual clients that had difficulties with the different chains Let's Encrypt offers. These are the short (alternate) and long (default) chains. The alternate chain was added to assist clients that did not handle the expired DST Root CA X3 in the chain that helped with older Androids.

You have not described which chain the syncplay server sends. And, it's not clear which CA Root store your client uses. Some client TLS packages have their own store.

It is difficult to debug that also given your need for secrecy and the opportunistic TLS. So, trying a different CA with an older root may work better. Just an idea. Maybe try ZeroSSL or another free CA.

To be clear, I am not being dismissive. It's just sometimes that's best.


depth=3 C = US, O = Internet Security Research Group, CN = ISRG Root X1
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X2
depth=1 C = US, O = Let's Encrypt, CN = E1
depth=0 CN =
(this was before the reset i described below)

The client uses the "certifi" root store, that is a Python port of mozilla's root store, which has isrg root x1.

no need for secrecy i just wanted to understand what's going wrong

Anyway, I got fed up and just wiped /etc/lestencrypt completely and started over from scratch (yeah that was a bit emotional to do...). And now.... It works :slight_smile:
So the problem was either the E1 Intermediate (but it doesn't make a lot of sense as the root is always x1), or some strange parameters such as using 4096 bits or elliptic curves... whatever.

The new chain is
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
depth=1 C = US, O = Let's Encrypt, CN = R3
depth=0 CN =

Which is also the same of the public servers, .
I guess I will back off from using E1 for now :slight_smile:

Thank you for your time :slight_smile:


I am pretty sure I have gotten this plot wrong but I am glad you are happy. Cheers


This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.