Why this happens
Since iOS 9, App Transport Security is on for every app by default. When URLSession — or anything built on it, including web content in WKWebView — is asked to load a plain http:// URL, ATS refuses the connection before a single byte leaves the device and logs:
Transport security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
The request fails with NSURLErrorAppTransportSecurityRequiresSecureConnection (code -1022). Your networking code is fine; the URL simply isn’t allowed by the app’s declared policy.
ATS is configured entirely through the NSAppTransportSecurity dictionary in the app’s Info.plist. There is no runtime API to opt out and no build setting that overrides it. If the dictionary is absent, the default policy applies: HTTPS only, TLS 1.2 or later, forward secrecy required.
How to fix it
1. Add an exception for your domain (the fix to ship)
In Xcode, select your app target, open the Info tab, and add these rows:
- App Transport Security Settings (
NSAppTransportSecurity) — type Dictionary. - Inside it, Exception Domains (
NSExceptionDomains) — type Dictionary. - Inside that, a row named after your host,
example.com— type Dictionary. - Inside the host, Allow Insecure HTTP Loads (
NSExceptionAllowsInsecureHTTPLoads) — type Boolean,YES. - Optional but usually wanted: Include Subdomains (
NSIncludesSubdomains) — Boolean,YES.
The resulting XML looks like this:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>example.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
Two details trip people up constantly:
- The dictionary key must be the host only.
https://example.com,example.com/apiorexample.com:8080will be silently ignored. Writeexample.com. - A subdomain such as
api.example.comis a different host. Either add its own entry or setNSIncludesSubdomainson the parent.
If the server’s TLS is old but you still want HTTPS, the per-domain dictionary also accepts NSExceptionMinimumTLSVersion (for example TLSv1.1) and NSExceptionRequiresForwardSecrecy (NO). Prefer fixing the server over relaxing these.
2. Allow arbitrary loads (debugging only)
If you just need the app to talk to anything while you’re wiring up a backend, a blanket exception is one key:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
This disables ATS for every request the app makes, which is why it should not survive into a release build. It weakens security across the whole app and may prompt questions during App Review, so treat it as a temporary switch.
One behaviour worth knowing: on iOS 10 and later, if you set NSAllowsArbitraryLoads alongside any of NSAllowsArbitraryLoadsInWebContent, NSAllowsArbitraryLoadsForMedia or NSAllowsLocalNetworking, the NSAllowsArbitraryLoads value is ignored. If a blanket exception “stops working” after you add one of those keys, that’s why.
3. Handle local development servers separately
For a dev server on your Mac or on the local network, use the narrow key instead of a blanket exception:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
NSAllowsLocalNetworking covers unqualified hostnames and .local names without opening up the public internet. To keep even that out of your shipping app, give the Debug configuration its own plist: duplicate Info.plist, add the ATS keys to the copy, and point the Debug configuration at it with the INFOPLIST_FILE build setting (Target → Build Settings → search “Info.plist File”). Release keeps the strict default.
4. Confirm the key actually reached the built app
Editing the plist does nothing until the app is rebuilt, and it’s easy to edit a copy that isn’t the one being compiled. Check the built product:
plutil -p ~/Library/Developer/Xcode/DerivedData/YourApp-*/Build/Products/Debug-iphonesimulator/YourApp.app/Info.plist \
| grep -A 12 NSAppTransportSecurity
If the dictionary isn’t there, you edited the wrong file or the build didn’t pick up the change — run Product → Clean Build Folder and build again. If you pasted raw keys, make sure the file is being edited as source: right-click Info.plist in the navigator → Open As → Source Code.
To see the failure and the fix side by side, this snippet prints the exact error code:
import Foundation
let url = URL(string: "http://example.com/health")!
URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error as NSError? {
// NSURLErrorDomain / -1022 means ATS blocked the request
print(error.domain, error.code)
} else {
print("loaded", data?.count ?? 0, "bytes")
}
}.resume()
5. The better long-term fix: serve over HTTPS
Every exception you add is a permanent hole in the app’s transport policy. Moving the endpoint to HTTPS with a current TLS configuration removes the need for NSAppTransportSecurity entirely, keeps App Review happy, and is the only option that protects your users on hostile networks. Use the per-domain exception as a stopgap while the server catches up, not as the final state.
How to prevent it
- Point new endpoints at
https://from the start; ATS then needs no configuration at all. - Never ship
NSAllowsArbitraryLoads— keep it in a Debug-only plist or delete it before archiving. - Use the host name alone in
NSExceptionDomains, and remember subdomains needNSIncludesSubdomains. - Add a CI check that greps the release
Info.plistforNSAllowsArbitraryLoadsand fails the build if it appears. - When a request fails with
-1022, read it as a configuration problem, not a networking bug — check the plist before touchingURLSessioncode.
FAQ
Does ATS apply to WKWebView content?
Yes. Web content loaded in WKWebView is subject to ATS, and a cleartext page will fail the same way. NSAllowsArbitraryLoadsInWebContent relaxes ATS for web views only, without affecting your URLSession traffic.
I added the exception but the error is unchanged. Why?
Check three things: the domain key is the bare host with no scheme, path or port; subdomains are covered by NSIncludesSubdomains or their own entry; and the built app’s Info.plist actually contains the dictionary. A stale build is the most common cause.
Is NSAllowsArbitraryLoads allowed on the App Store?
It can pass, but it disables ATS for every connection your app makes and invites scrutiny during review. Per-domain exceptions are the safer choice, and HTTPS is better than both.
Does this affect macOS apps too?
Yes. App Transport Security applies to macOS apps built with the modern SDK, and the same NSAppTransportSecurity keys in Info.plist control it.
Parts of this guide are adapted from content licensed under CC BY-SA 3.0; this article is shared under the same license.
