DEV Community

raykim
raykim

Posted on

[iOS] Debugging SSL Handshake Failures

The Problem: An Unexpected Configuration Conflict

Recently, our monitoring dashboard started lighting up with sporadic network error logs. They weren't your typical 404s or 500s. They were fragmented, low-level errors pointing to one specific culprit: SSL Handshake Failures.

This was unexpected because our ATS (App Transport Security) configuration appeared to be fully permissive. We had explicitly set NSAllowsArbitraryLoads to true in our Info.plist.

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>
Enter fullscreen mode Exit fullscreen mode

Theoretically, this setting is supposed to bypass ATS checks, allowing connections to servers with self-signed certificates or older TLS versions. However, despite this configuration, we were still seeing persistent TLS/SSL network errors.

After digging through the documentation, I discovered a hidden override in Apple's configuration rules.

The Discovery: The InWebContent Override

The root cause lay in another key we had enabled for our WebViews: NSAllowsArbitraryLoadsInWebContent.

We had enabled this to allow mixed content in our in-app browser. However, a crucial detail in the Apple Documentation states:

"If NSAllowsArbitraryLoadsInWebContent is present, the value of NSAllowsArbitraryLoads is overridden to NO."

Because we target iOS 10+, the presence of the InWebContent key was silently overruling our global "Allow All" setting.

  1. Inside WebViews: Arbitrary loads were allowed.
  2. Native Networking (API calls, Image loading): The global flag was ignored, so ATS reverted to its default, strict mode.

This meant our app was enforcing strict ATS policies (TLS 1.2+, Forward Secrecy required) for all API calls, regardless of our intention to allow arbitrary loads.

Why TLS Settings Were the Suspect

Once it became clear that strict ATS was active, the "SSL Handshake Failure" logs made perfect sense.

  • The App (Client): Enforcing TLS 1.2 or higher (ATS Default).
  • The Server: A legacy server capable of speaking only TLS 1.0.
  • The Result: Negotiation failed. Connection dropped.

We were inadvertently blocking our own legacy servers because the app's security standards were silently defaulted to maximum.

You might be thinking, "Who even runs TLS 1.0 servers in 2026?"
More services than you'd expect. That old payment gateway your finance team refuses to migrate? TLS 1.0. The municipal API for address verification? TLS 1.0. That ad SDK you integrated three years ago and forgot about? Yep, probably TLS 1.0.

The Misconception: "Insecure" ≠ "Old Protocol"

A common approach to fixing this is enabling NSExceptionAllowsInsecureHTTPLoads for the problematic domains. It is often assumed that allowing "Insecure Loads" disables all checks, including TLS version requirements.

This is incorrect.

NSExceptionAllowsInsecureHTTPLoads allows HTTP (unencrypted) connections to the specified domain.

However, it does not:

  • Lower the TLS version requirement (HTTPS still requires TLS 1.2)
  • Bypass certificate validation (self-signed or expired certs will still fail)

If the server only speaks TLS 1.0, the handshake will still fail regardless of this setting.

The Solution: Surgical Configuration

Instead of trying to force a global "Allow All" again (which is generally bad practice), I applied a surgical fix.

The goal was to:

  1. Keep security high: Certificate verification must remain active.
  2. Lower compatibility barriers: Explicitly allow older protocols for specific legacy domains.

Here is the final Info.plist configuration:

<key>NSExceptionDomains</key>
<dict>
    <key>legacy-api.example.com</key>
    <dict>
        <key>NSExceptionMinimumTLSVersion</key>
        <string>TLSv1.0</string>
        <key>NSExceptionRequiresForwardSecrecy</key>
        <false/>
        <key>NSIncludesSubdomains</key>
        <true/>
    </dict>
</dict>
Enter fullscreen mode Exit fullscreen mode

This configuration instructs the app to check the certificate strictly, but to accept connections even if they use the older TLS 1.0 protocol.

Deploy & Next Steps

The updated configuration has been deployed.
If the hypothesis is correct, the SSL/TLS errors for those legacy domains should vanish. If the errors persist, there may be another layer to the problem—perhaps a cipher suite mismatch or an intermediate certificate issue.

I'll update this post with the results. Fingers crossed this is the end of the saga! 🤞


Summary

  1. Watch out for overrides: NSAllowsArbitraryLoadsInWebContent silently disables NSAllowsArbitraryLoads.
  2. Know your keys: InsecureHTTPLoads allows HTTP connections, but NSExceptionMinimumTLSVersion is required for TLS version issues.
  3. Be specific: Don't disable security globally. Configure exceptions only for the domains that actually need them.

References

https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW35

Top comments (0)