Fix ESP32 “X509 - Certificate verification failed” (-9984)
The ESP32 does not trust the server’s certificate. Give WiFiClientSecure the site’s root CA (not the site certificate), or use the built-in CA bundle.
Community draft — report a mistake
What you see
Connecting to api.telegram.org...
[ 10512][E][ssl_client.cpp:37] _handle_error(): [start_ssl_client():273]: (-9984) X509 - Certificate verification failed, e.g. CRL, CA or signature check failed
[ 10520][E][NetworkClientSecure.cpp:159] connect(): start_ssl_client: -9984
Connection failedConnecting to api.telegram.org... [ 10512][E][ssl_client.cpp:37] _handle_error(): [start_ssl_client():273]: (-9984) X509 - Certificate verification failed, e.g. CRL, CA or signature check failed [ 10520][E][NetworkClientSecure.cpp:159] connect(): start_ssl_client: -9984 Connection failed
HTTPS only works if the board can check that the server is who it claims to be. It does that by following the server’s certificate chain up to a root certificate you gave it. -9984 means it reached the top of the chain and found no match.
#include <WiFiClientSecure.h>
const char* ROOT_CA = R"PEM(
-----BEGIN CERTIFICATE-----
...paste the root CA here...
-----END CERTIFICATE-----
)PEM";
WiFiClientSecure client;
client.setCACert(ROOT_CA);Quick fix — try in order
- 1
Use the ROOT certificate, not the site’s own
In the browser’s padlock → certificate viewer, export the top certificate of the chain (e.g. ISRG Root X1, GTS Root R1, DigiCert Global Root G2) as PEM and pass that to
client.setCACert(). Leaf certificates expire every few months. - 2
Check it is the right root for that host
Different sites use different CAs, and a site can switch CA. If it worked before and stopped, the server changed its chain — re-export the root.
- 3
Copy the PEM exactly
Keep the
-----BEGIN CERTIFICATE-----and END lines and a newline after each line (\nin a C string, or a raw string literal). - 4
Or use the core’s CA bundle
Recent Arduino-ESP32 cores ship a bundle of common root CAs (see the core’s WiFiClientSecure examples). Attach it and most public sites verify without exporting anything.
- 5
setInsecure() only for a quick test
client.setInsecure()skips verification. Fine to prove the rest of the code works; don’t ship it — anyone on the network can then impersonate the server.
Why it happens
What is -9984?
mbedTLS error -0x2700, MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: the server’s certificate chain does not lead to a CA you gave the client.
No CA set at all
A WiFiClientSecure with no CA, no bundle and no setInsecure() cannot verify anything and fails like this.
Related: (-76) UNKNOWN ERROR CODE (004C)
That is a network read timeout during the TLS handshake (weak Wi-Fi or a slow server), not a certificate problem.
Board-specific notes
ESP32: The log file and line numbers (ssl_client.cpp:37, start_ssl_client():273) vary between core versions; the (-9984) code is what matters.
ESP8266: Uses BearSSL: BearSSL::X509List cert(rootCA); client.setTrustAnchors(&cert); and it also needs the correct time (NTP) to check dates — see NTP time shows 1970.
Did this fix it?
Thanks — that helps us keep this page right.