Guide
Flutter app certificate expired errors
Flutter certificate verify failed when Dio or http client rejects an expired API TLS cert. Verify the API hostname live, renew and reload the server, then retest on device without badCertificateCallback.
Updated May 2, 2026 802 words
Flutter certificate verify failed on Dio,
package:http, orHttpClientmeans the Dart VM rejected the server chain—often because the API HTTPS listener’snotAfteris past, though untrusted private CAs produce similarHandshakeExceptiontext.
Mobile users update apps slowly; backend cert expiry breaks every installed build at once without a store release. Flutter uses the platform TLS stack on iOS (Security.framework) and Android (Conscrypt), plus Dart’s verifier for pure Dart IO.
Do not conflate TLS failures with Firebase or GraphQL errors higher in the stack. Log the underlying socket or handshake exception message first and preserve the hostname string in crash breadcrumbs so backend logs can correlate without guessing which API cluster the device used.
Product owners deciding on forced upgrade prompts should wait for proof that server certificates are valid for the existing app generation. Forcing upgrades during server expiry frustrates users without fixing PKIX until notAfter on the listener actually moves forward.
First response
Capture Flutter version, target platform (iOS/Android), release versus debug, and full API base URL including path prefixes. Save one failing request from Dio logging interceptor or HttpClient error string.
Test the same URL with curl from your laptop and from a device browser if possible. Record device clock—mobile wrong time zones rarely matter, but manual clock edits do.
Check whether badCertificateCallback or custom SecurityContext is enabled in debug-only code paths accidentally shipped in a profile build.
Note MITM proxies on corporate devices and whether pinning (HttpClient certificate pins) blocks renewed certs with new public keys.
For flavor-specific android/app/src/*/res/xml/network_security_config.xml, confirm production flavor does not reference debug-only trust anchors. iOS builds need matching NSAppTransportSecurity exceptions audited—exceptions are not a substitute for renewing server certs.
Capture whether failure is on REST, GraphQL, or WebSocket channels because each path uses the same verifier but different retry logic in application code.
Support macros should ask for app version and platform before suggesting reinstall, yet escalate immediately to platform teams when external OpenSSL already shows expired notAfter on the API host. Mobile incidents create social media pressure quickly; status updates that name the API hostname and estimated recovery window calm users better than generic maintenance language that does not mention TLS.
What this error means
When HttpClient opens a TLS socket, platform trust stores validate the server chain and expiration before Dart receives HTTP bytes. Expired leaf or intermediate triggers verification failure; Dio wraps this as DioException with connection error type.
Certificate pinning compares server SPKI to embedded pins—renewal with same key may work, but reissue with new key fails until app update even if dates are valid. Separate pinning incidents from calendar expiry by checking whether OpenSSL shows past notAfter.
Flutter Web uses the browser engine TLS stack entirely. Errors appear in the JavaScript console as standard certificate warnings that mirror Chrome behavior for the same API hostname. Native iOS and Android platforms are the primary focus for this guide.
Cross-reference Android SSL certificate expired when only older API levels fail due to chain issues, though expiry should fail all versions uniformly.
HttpOverrides.global with bad callbacks affects every client in the isolate—remove global overrides in release entrypoints. Certificate transparency does not help mobile apps directly; rely on openssl proof on API host.
Dio onHttpClientCreate hooks that inject self-signed trust for one endpoint can leak to others if the hook is shared—grep release branches for leftover debugging hooks before escalating to infrastructure.
Common causes
Mobile release cadence lengthens the blast radius of API certificate mistakes because users do not reinstall weekly. Operations should therefore prioritize confirming server TLS before asking consumers to update apps, delete caches, or toggle airplane mode. Crash reports that mention HandshakeException without hostname metadata still deserve the same external OpenSSL proof used for backend incidents, because client stack traces alone rarely identify which API cluster served expired material.
API behind Cloudflare or API gateway renewed on origin but edge custom cert expired. Mobile apps still call legacy hostname after DNS cutover to a cluster serving default ingress cert with wrong SAN.
Internal CA-signed cert on a customer-facing API—Android without user-installed root fails with verify failed wording. Debug charles proxy cert forgotten enabled on device.
Staging cert in flavor configuration promoted to production build pipeline. BFF and microservice hostname mismatch: app hits api while TLS cert covers only bff.
Backend team renewed PEM but pod ingress not reloaded—same pattern as Electron certificate errors on desktop clients calling the same API.
Store review cycles delay shipping a pinning fix even after the server is healthy. Prefer public PKI on customer-facing API hostnames so mobile releases are decoupled from routine ninety-day renewals unless product security mandates pinning with an owned rotation runbook.
Confirm from the outside
OpenSSL
API=api.example.com
echo | openssl s_client -connect "$API:443" -servername "$API" 2>/dev/null \
| openssl x509 -noout -dates -subject -issuer -ext subjectAltName
curl
curl -vI "https://$API/v1/health" 2>&1 | grep -Ei 'expire|SSL|certificate'
Dart one-off (developer machine)
import 'dart:io';
void main() async {
final client = HttpClient();
try {
final req = await client.getUrl(Uri.parse('https://api.example.com/health'));
final res = await req.close();
print(res.statusCode);
} catch (e) {
print(e);
} finally {
client.close();
}
}
Match mobile failure text with desktop Dart IO using same network.
iOS ATS diagnostics
Attempt URL in Safari on the device; if Safari blocks with expired cert, Flutter will too.
Fix checklist
- Prove past
notAfteron the hostname the app calls, not ancillary CDN marketing domains. - Renew certificate including full chain on API terminator; update ingress or load balancer bindings.
- Reload services; confirm new serial via OpenSSL before mobile retest.
- Remove
badCertificateCallbackthat returns true in any release build flavor. - Update certificate pins only if you intentionally rotate SPKI—coordinate app release if keys change.
- Re-test on physical devices on cellular and Wi-Fi without debug proxies.
- Add API hostname to live TLS monitoring separate from marketing site checks.
Instrument release candidates with integration tests that call production API hostnames from Firebase Test Lab or internal device farms. A green CI unit test with mocked HTTP does not exercise TLS; nightly jobs that hit real HTTPS catch expiry before end users on older app versions do.
When using GraphQL gateways, confirm the mobile app talks to the same hostname covered by the renewed cert, not a legacy subdomain still routed to deprecated ingress. BFF layers that rewrite paths but not TLS SNI are a common hiding place for forgotten certificates.
Product and backend leads should agree on the single canonical API hostname before closing mobile incidents, otherwise store builds drift toward alternate URLs. After server renewal, publish internal release guidance clarifying that users do not need to update the app when only server certificates rotated on the same hostname. If pins or custom trust were temporarily used in beta, schedule a follow up release removing them once production TLS is publicly trusted again.
When to use automation
Consumer mobile apps amplify operational mistakes because every installed version points at the same API until deliberately reconfigured. Product analytics should include a lightweight daily TLS probe from a device farm or emulator fleet hitting production API hostnames with verification enabled. Marketing launches that spike new installs make latent expiry problems visible within hours rather than days when those probes run continuously instead of only during release week.
Mobile clients will not email you before certificates lapse. External monitoring on API SANs with free TLS check catches expiry before app store reviews could help. Alert at 30, 14, 7, and 1 days on every hostname mobile builds embed in flavors.
Track RDAP on those domains when using domain-validated reissue paths that depend on registrar continuity.
Mobile release trains should include a TLS smoke test job keyed off the same API base URL constants production flavors embed at build time. Staging flavors that point at expired certificates should fail that job before artifacts reach testers, which prevents false confidence from developers who trust additional roots on laptops.
Customer support should receive a one line proof artifact after renewal, usually openssl notAfter text, to attach when closing widespread mobile tickets without asking users to perform technical troubleshooting steps. When monitoring fires, link the incident ticket to the exact flavor file supplying BASE_URL so mobile and backend teams agree on hostname scope.
Frequently asked questions
Why iOS works but Android fails?
Different trust roots or pinning configs per platform build.gradle versus Info.plist. Also check whether Android uses a user-installed debug cert.
Can I ship a private CA in the app?
Possible via bundled assets and SecurityContext, but public APIs should use public CAs. Maintain rotation for internal schemes.
Is Dio `validateStatus` related?
No—that is HTTP status after TLS succeeds. Fix handshake first.
Does Firebase Hosting affect API TLS?
Firebase Hosting certs are separate from your REST API host. Monitor each hostname the app calls.
See what the server presents right now
Free live certificate check — no signup.