Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.
HTTPS · WiFiClientSecure

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.

ESP32ESP32-C3ESP32-S3Updated 3 Nov 2026

Community draft — report a mistake

What you see

OutputOutput
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 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. 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. 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. 3

    Copy the PEM exactly

    Keep the -----BEGIN CERTIFICATE----- and END lines and a newline after each line (\n in a C string, or a raw string literal).

  4. 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. 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.

↑↓ to move · Enter to open