Why do OkHttp calls fail with SSL errors?

OkHttp SSL errors are raised during the TLS handshake, the short negotiation that happens before any HTTP request is sent. They mean the client could not verify the server’s certificate, found a defect in it — expired, issued for a different name, or signed by an authority it does not trust — or failed to agree with the server on encryption settings. The request itself is never delivered, which is why these failures look so different from ordinary HTTP errors.

The messages have a reputation for being cryptic, but each one names the check that failed. Once you can read them, most cases reduce to a short list of causes: a broken certificate chain on the server, a wrong clock on the device, a name mismatch, a device negotiating outdated TLS, or a certificate pin that no longer matches the server.

This article walks through those causes and the safe fix for each, including the one response that is never acceptable: switching verification off. If you want the background first, our overview of OkHttp HTTPS support and API requests explains how secure connections are supposed to work in OkHttp.

The Handshake, in Plain Words

When your app opens an HTTPS connection, the server presents a certificate: a small signed document stating its identity and its public key. The client checks that the certificate was issued by an authority it trusts, that it has not expired, and that it actually belongs to the host being contacted. The two sides then use it to agree on encryption keys, and only after all of that does the HTTP request travel, protected, over the connection.

Every one of these errors is a failure of one of those checks, or of the encryption negotiation itself. That framing is genuinely useful in practice. Instead of treating the exception as random breakage, ask which check the message describes, then fix that specific thing rather than changing settings at random.

“Trust Anchor Not Found”: Certificates the Client Cannot Verify

The most common handshake failure reports that a trust anchor for the certification path was not found, wrapped in a certificate path validation exception. In plain terms, the client followed the certificate’s chain of issuers upward and never arrived at an authority it trusts.

The usual culprit is a self-signed certificate, common on development servers and internal tools, or a certificate issued by a private company authority that the device has never heard of. A public website showing this error almost always has a server-side configuration problem, and the correct fix is there: install a certificate from a properly trusted authority, or have the private authority deliberately installed on the devices that need it. The error is the security system working as designed, not a bug in the client.

A Broken Chain: The Missing Middle Certificate

Servers are supposed to present not just their own certificate but also the intermediate certificates that link it to a trusted root. Some servers are misconfigured and send only their own certificate. The result is the same trust anchor error, even though the certificate itself is perfectly legitimate.

This gap explains the classic report that a site works in a browser but not in the app. Browsers sometimes fetch a missing intermediate automatically, or already have it cached from other sites; an HTTP client validates exactly what it was given. The fix belongs to whoever runs the server: serve the complete chain. Until they do, no client-side change that keeps verification intact can make the call succeed — and that is by design.

Expired Certificates and Wrong Clocks

A certificate is valid only between two dates, and the client compares those dates against the device’s clock. Two very different problems produce the same failure. The server’s certificate may genuinely have expired — an operations lapse its owners must fix by renewing it — or the device’s date and time may be wrong, making a healthy certificate look expired, or not yet valid.

Wrong clocks are surprisingly common on test devices, spare handsets and hardware that has been sitting in a drawer with a flat battery. Before suspecting anything exotic, check the device’s date and time; switching on automatic date and time fixes an entire class of mysterious handshake failures. It is also why apps should surface certificate problems honestly instead of reporting a generic network failure: sometimes the fix sits in the user’s own settings.

The Name on the Certificate Does Not Match

After the chain checks out, OkHttp verifies that the certificate was issued for the host the app actually contacted. A certificate for one subdomain does not cover a sibling subdomain unless it is a wildcard, and a certificate does not cover a raw IP address unless that address is explicitly listed in it. Failure here produces a peer unverified exception reporting that the hostname was not verified.

This typically appears after infrastructure changes: the app now points at a new host name, a redirect leads somewhere the certificate does not cover, or a staging certificate ends up on a production host. The correct fixes are to contact the host the certificate was issued for, or to issue a certificate covering the names actually used. What must never be used to “fix” it is a hostname verifier that accepts everything; that check is the only thing ensuring the verified certificate belongs to the server you meant to call.

When the Two Sides Cannot Agree on TLS

Some handshake failures are about protocol rather than identity. Very old Android versions shipped with support for newer TLS versions present but not always enabled, while modern servers increasingly refuse anything older than TLS 1.2. At the other end, a client configured for modern settings will not fall back to the obsolete protocols a legacy server offers. Either way, the negotiation ends with a handshake or protocol exception and no connection at all.

These failures cluster on old devices talking to recently hardened servers. The remedies are unglamorous: keep devices and their system components updated where possible, and run servers that accept current TLS versions, which any maintained server should already do. If you support very old hardware, test against it explicitly, because this is the class of SSL problem that no amount of certificate debugging will reveal.

Certificate Pinning and the Day the Pins Go Stale

Certificate pinning adds a second, stricter check on top of normal verification: the client compares the server’s certificate or public key against a small set of expected hashes, and refuses the connection if none match. It guards against a trusted-but-wrong certificate being used to impersonate your server. Pinning failures surface as a peer unverified exception announcing a certificate pinning failure.

The trade-off is operational. When the server’s certificate is replaced — a routine renewal, a new provider, an emergency rotation — the pins inside already-installed apps stop matching, and those apps lose connectivity until they are updated. Pinning therefore has to be planned like a deployment feature: keep a backup pin for the next certificate configured in advance, and coordinate rotations with app releases.

val pinner = CertificatePinner.Builder()
    .add("api.example.com", "sha256/your-pin-here")
    .build()

val client = OkHttpClient.Builder()
    .certificatePinner(pinner)
    .build()

Cleartext Errors Are a Different Animal

One error is frequently confused with SSL failures but involves no TLS at all. If an app requests a plain HTTP address and the platform forbids cleartext traffic, the call fails immediately with a message stating that cleartext communication is not permitted. Modern Android versions block cleartext by default for apps built for recent platform versions, so this often appears after a platform upgrade rather than after any server change.

The distinction matters because the fixes are opposites. An SSL error means the secure connection could not be established; a cleartext error means no secure connection was even attempted. Use HTTPS addresses, and treat any proposal to simply permit cleartext everywhere with the same suspicion as disabling certificate verification: it removes a protection instead of fixing a configuration.

The Tempting Fix That Must Never Ship

Search for any of these errors and you will quickly find code that makes them vanish: a trust manager that trusts every certificate, a hostname verifier that returns true for everything, sometimes packaged with the assurance that it is “for development only”. Installing such code does remove the error — by removing the verification that produced it.

An app built that way accepts any certificate from anyone, which is precisely the man-in-the-middle attack that TLS exists to prevent, and this kind of snippet has a long history of escaping development builds into production releases. Treat its presence in a codebase as a security bug to delete, not a solution to keep. Every genuine cause in this article has a real fix; the error appearing is the system telling you something true.

A Safe Way to Debug an SSL Failure

Work through the problem in the order the client performs its checks. First, read the exact exception and its message; they usually identify the failing check directly. Second, confirm the device’s date and time are correct and set to update automatically. Third, have the server checked for a complete certificate chain, and for a certificate that is in date and covers the host name being called.

Fourth, establish which TLS versions the server accepts and what the oldest device you support can offer. Fifth, if the app pins certificates, compare the configured pins with the certificate the server currently presents, and check whether a rotation happened without a backup pin waiting. Almost every one of these failures resolves at one of those five steps, and none of them requires weakening the client.

Frequently Asked Questions

Why does the same address work in a browser but fail in my app?

Usually because the two clients are not checking under identical conditions. A browser may fetch or already hold a missing intermediate certificate that the server failed to send, while the app validates only what it receives. The app may also enforce certificate pins the browser knows nothing about, or the device may offer older TLS settings to apps than the browser uses. Each possibility points at a different fix, so compare the exact error first.

Does an SSL error mean the server is down?

No. An SSL error means the server was reached but the secure negotiation failed, so no HTTP request was exchanged. A server that is genuinely down normally produces a connection failure instead — refused, unreachable or timed out. Reading the exception type before assuming an outage saves a great deal of misplaced debugging.

How can my app talk to a test server that uses a self-signed certificate?

By trusting that specific certificate or its issuing authority deliberately, and only there. Install the local authority on your test devices, or configure test builds of the app to trust that one authority explicitly. What you must not do is make the client trust everything, and release builds should never carry the test exception at all.

Can certificate pinning cause errors even when the server is healthy?

Yes, and it is one of the most common causes of sudden, app-only outages. If the server’s certificate is rotated and the app holds no pin for the new one, every pinned connection is refused even though the server and its new certificate are perfectly valid. Keeping a backup pin configured ahead of time is the standard protection.

Conclusion

OkHttp SSL errors are specific complaints, not random breakage: a chain that could not be verified, a name that did not match, dates that did not line up, a protocol negotiation that failed, or a pin that no longer fits. Each cause has a proper fix, and most of those fixes live on the server or in deployment planning rather than in your networking code.

So keep verification fully intact, read the message before changing anything, and check the humble causes — clocks and chains — before the exotic ones. An app whose TLS fails loudly, and then gets fixed properly, keeps its users’ data protected; an app whose errors were silenced does not.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top