# NSURLErrorDomain Code=-1202: certificate untrusted on iOS

> NSURLErrorDomain -1202 means an iOS or macOS app rejected the server's certificate: untrusted root, wrong hostname, missing intermediate or Apple policy.

Source: https://ssl.sh/errors/ios-nsurlerrordomain-1202/
Last updated: 2026-11-10

**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](https://ssl.sh/errors/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](https://ssl.sh/errors/safari-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:

```bash
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:

```bash
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](https://ssl.sh/tools/chain-builder/); see [missing intermediate certificate](https://ssl.sh/errors/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](https://ssl.sh/tools/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

```bash
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](https://ssl.sh/tools/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.
