Fix: Transport Security Has Blocked a Cleartext HTTP Resource Load

Fix: Transport Security Has Blocked a Cleartext HTTP Resource Load

Written by

in

Quick answer: App Transport Security blocks plain http:// loads by default, and the only way to allow one is the NSAppTransportSecurity dictionary in Info.plist. Add an NSExceptionDomains entry for your host with NSExceptionAllowsInsecureHTTPLoads set to true — or, for quick local testing only, set NSAllowsArbitraryLoads to true.

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:

  1. App Transport Security Settings (NSAppTransportSecurity) — type Dictionary.
  2. Inside it, Exception Domains (NSExceptionDomains) — type Dictionary.
  3. Inside that, a row named after your host, example.com — type Dictionary.
  4. Inside the host, Allow Insecure HTTP Loads (NSExceptionAllowsInsecureHTTPLoads) — type Boolean, YES.
  5. 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/api or example.com:8080 will be silently ignored. Write example.com.
  • A subdomain such as api.example.com is a different host. Either add its own entry or set NSIncludesSubdomains on 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 need NSIncludesSubdomains.
  • Add a CI check that greps the release Info.plist for NSAllowsArbitraryLoads and 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 touching URLSession code.

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.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 3.0; this article is shared under the same license.