Phones and operating systems

NSURLErrorDomain Code=-1202: certificate untrusted on iOS

By · Last verified

Error messageNSURLErrorDomain Code=-1202

NSURLErrorDomain Code=-1202 means Apple's URL loading system did not trust the server's certificate. The constant is NSURLErrorServerCertificateUntrusted. Apple uses it for most failed trust evaluations: private or self-signed CAs, hostname mismatches, incomplete chains and certificates that break Apple's rules. The log line usually begins "The certificate for this server is invalid."

What iOS checks before trusting a server

Apple's documentation lists the checks of default server trust evaluation: the certificate's signature is intact, it has not expired, its name matches the server's DNS name, and it chains through valid certificates to a trusted anchor. That anchor must ship with the operating system or be installed on the device by the user or an administrator. Several different failures surface as the same -1202, so the code alone does not tell you which check failed; the steps below narrow it down.

Where -1202 sits among the TLS codes

Code Swift name Meaning
-1200 secureConnectionFailed Handshake failed, see NSURLErrorDomain -1200
-1201 serverCertificateHasBadDate Certificate expired or not yet valid
-1202 serverCertificateUntrusted Trust evaluation failed
-1203 serverCertificateHasUnknownRoot Not signed by any root
-1204 serverCertificateNotYetValid Certificate not yet valid
-1206 clientCertificateRequired Server wants a client certificate

Safari shows the same situation as This Connection Is Not Private.

Read the underlying SSL code in the log

URLSession logs often add a line such as NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813). That second number comes from Apple's Security framework and is more specific than -1202:

Code Constant What it means
-9807 errSSLXCertChainInvalid Invalid certificate chain
-9812 errSSLUnknownRootCert Valid chain, untrusted root
-9813 errSSLNoRootCert Chain not verified by a root, often a missing intermediate or private CA
-9814 errSSLCertExpired A certificate in the chain has expired
-9843 errSSLHostNameMismatch The host name does not match the certificate

Common causes, most frequent first

1. Development or internal server with a private CA

Staging APIs, on-premise servers and local machines often use self-signed certificates or an internal CA. Neither is in iOS's trust store. Look at the issuer:

openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer

2. Hostname or IP not covered by the certificate

The app calls https://203.0.113.10 or an alias that is not in the Subject Alternative Name. Since iOS 13, names in the Common Name are not trusted at all, so a certificate with the name only in the CN also fails. List the names:

openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -ext subjectAltName

3. Incomplete chain

The server sends only its own certificate. Some clients repair this; do not rely on it. Count the certificates with -showcerts or use the chain builder; see missing intermediate certificate.

4. The certificate breaks Apple's rules

Apple requires, for all TLS server certificates on iOS 13 and macOS 10.15 or later: RSA keys of at least 2048 bits, SHA-2 signatures, and the DNS name in the SAN. Certificates issued after July 1, 2019 also need the id-kp-serverAuth extended key usage and a validity of no more than 825 days. Publicly trusted certificates issued on or after September 1, 2020 may not exceed 398 days. Internal CAs that issue five-year certificates without an EKU fail here. Decode the certificate with the certificate decoder and check each item.

5. Debugging proxy or network interception

Charles, Proxyman, mitmproxy, corporate proxies and captive portals present their own certificates. Their root must be installed and fully trusted on the device, or the app fails with -1202.

Diagnose from a Mac

nscurl --ats-diagnostics --verbose https://api.example.com/

nscurl uses Apple's networking stack, so a trust failure there reproduces what the app sees. Compare with an external check from the SSL checker.

How to fix it

Fix the certificate or the chain on the server; never relax trust evaluation in a shipping app.

Public servers: use a certificate from a public CA, include every hostname the app uses in the SAN, and serve the full chain (fullchain.pem in Nginx).

Internal or development servers on test devices: 1. Issue certificates that follow Apple's rules: SAN with the DNS name, serverAuth EKU, SHA-256, at most 825 days. 2. Install the root CA on the device through a configuration profile or MDM. 3. For a profile installed by hand, open Settings > General > About > Certificate Trust Settings and enable full trust for that root. Profiles deployed through MDM or Apple Configurator are trusted automatically.

In the app: keep the default trust evaluation. Apple's guide to manual server trust authentication shows how a urlSession(_:didReceive:completionHandler:) delegate can inspect the server trust, but its documentation on insecure connections states that while ATS is enabled you can only tighten evaluation, for example to pin a key, not loosen it. For a private CA in production, distribute its root to managed devices instead of weakening checks in code. Never accept every certificate, not even temporarily in a release build.

How to verify the fix

  1. openssl s_client -connect api.example.com:443 -servername api.example.com -verify_return_error completes with Verify return code: 0 (ok) for public certificates.
  2. nscurl --ats-diagnostics https://api.example.com/ reports PASS for the default configuration.
  3. Run the request on a physical device as well as the simulator, since their trust stores differ.

FAQ

Can I add an ATS exception to Info.plist instead?

Not as a fix. Default server trust evaluation still requires a chain to an anchor that ships with the OS or was installed by the user or an administrator. Apple's documentation also notes that while ATS is enabled, an app can only tighten trust evaluation (for example with pinning), not loosen it. Fix the certificate or install the private root on the devices.

It works in the simulator but fails on a real device (or the reverse). Why?

The simulator and the device have separate trust stores. A development root installed on your Mac or in one simulator is not present on the device, and a root installed on a device from a profile still needs full trust enabled in Certificate Trust Settings.

Why does -1202 appear when I connect by IP address?

The certificate lists DNS names, not that IP, so the hostname check fails. Public CAs issue IP certificates only for public addresses. Connect by a DNS name that the certificate covers.

How is -1202 different from -1200?

-1200 (NSURLErrorSecureConnectionFailed) means the TLS handshake failed, often because of ATS protocol or cipher rules. -1202 means the handshake got as far as the certificate and trust evaluation rejected it.

Sources

  1. Apple Developer: URLError.Code.serverCertificateUntrusted
  2. Apple Support: Requirements for trusted certificates in iOS 13 and macOS 10.15
  3. Apple Support: About upcoming limits on trusted certificates
  4. Apple Support: Trust manually installed certificate profiles
  5. Apple Developer: Performing manual server trust authentication
  6. Apple Developer: errSSLHostNameMismatch
  7. Apple Developer: Preventing insecure network connections