<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: raykim</title>
    <description>The latest articles on Forem by raykim (@raykim2414).</description>
    <link>https://forem.com/raykim2414</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3700633%2F9fd6b79b-2d55-43a5-a9de-74868b8bdade.png</url>
      <title>Forem: raykim</title>
      <link>https://forem.com/raykim2414</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/raykim2414"/>
    <language>en</language>
    <item>
      <title>[iOS] Debugging SSL Handshake Failures</title>
      <dc:creator>raykim</dc:creator>
      <pubDate>Sun, 11 Jan 2026 12:03:20 +0000</pubDate>
      <link>https://forem.com/raykim2414/ios-debugging-ssl-handshake-failures-3jf0</link>
      <guid>https://forem.com/raykim2414/ios-debugging-ssl-handshake-failures-3jf0</guid>
      <description>&lt;h2&gt;
  
  
  The Problem: An Unexpected Configuration Conflict
&lt;/h2&gt;

&lt;p&gt;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: &lt;strong&gt;SSL Handshake Failures&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This was unexpected because our ATS (App Transport Security) configuration appeared to be fully permissive. We had explicitly set &lt;code&gt;NSAllowsArbitraryLoads&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; in our &lt;code&gt;Info.plist&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSAppTransportSecurity&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSAllowsArbitraryLoads&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;After digging through the documentation, I discovered a hidden override in Apple's configuration rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Discovery: The &lt;code&gt;InWebContent&lt;/code&gt; Override
&lt;/h2&gt;

&lt;p&gt;The root cause lay in another key we had enabled for our WebViews: &lt;strong&gt;&lt;code&gt;NSAllowsArbitraryLoadsInWebContent&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"If &lt;code&gt;NSAllowsArbitraryLoadsInWebContent&lt;/code&gt; is present, the value of &lt;code&gt;NSAllowsArbitraryLoads&lt;/code&gt; is overridden to NO."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because we target iOS 10+, the presence of the &lt;code&gt;InWebContent&lt;/code&gt; key was silently overruling our global "Allow All" setting.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Inside WebViews:&lt;/strong&gt; Arbitrary loads were allowed.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Native Networking (API calls, Image loading):&lt;/strong&gt; The global flag was ignored, so ATS reverted to its &lt;strong&gt;default, strict mode&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why TLS Settings Were the Suspect
&lt;/h2&gt;

&lt;p&gt;Once it became clear that strict ATS was active, the "SSL Handshake Failure" logs made perfect sense.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The App (Client):&lt;/strong&gt; Enforcing &lt;strong&gt;TLS 1.2&lt;/strong&gt; or higher (ATS Default).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Server:&lt;/strong&gt; A legacy server capable of speaking only &lt;strong&gt;TLS 1.0&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Result:&lt;/strong&gt; Negotiation failed. Connection dropped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We were inadvertently blocking our own legacy servers because the app's security standards were silently defaulted to maximum.&lt;/p&gt;

&lt;p&gt;You might be thinking, &lt;strong&gt;"Who even runs TLS 1.0 servers in 2026?"&lt;/strong&gt;&lt;br&gt;
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.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Misconception: "Insecure" ≠ "Old Protocol"
&lt;/h2&gt;

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

&lt;p&gt;&lt;strong&gt;This is incorrect.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;NSExceptionAllowsInsecureHTTPLoads&lt;/code&gt; allows &lt;strong&gt;HTTP&lt;/strong&gt; (unencrypted) connections to the specified domain.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;it does not:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Lower the TLS version requirement (HTTPS still requires TLS 1.2)&lt;/li&gt;
&lt;li&gt;  Bypass certificate validation (self-signed or expired certs will still fail)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the server only speaks TLS 1.0, the handshake will still fail regardless of this setting.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Solution: Surgical Configuration
&lt;/h2&gt;

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

&lt;p&gt;The goal was to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Keep security high:&lt;/strong&gt; Certificate verification must remain active.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Lower compatibility barriers:&lt;/strong&gt; Explicitly allow older protocols for specific legacy domains.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the final &lt;code&gt;Info.plist&lt;/code&gt; configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSExceptionDomains&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;legacy-api.example.com&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSExceptionMinimumTLSVersion&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;TLSv1.0&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSExceptionRequiresForwardSecrecy&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;false/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSIncludesSubdomains&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration instructs the app to &lt;strong&gt;check the certificate strictly&lt;/strong&gt;, but to &lt;strong&gt;accept connections even if they use the older TLS 1.0 protocol&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy &amp;amp; Next Steps
&lt;/h2&gt;

&lt;p&gt;The updated configuration has been deployed.&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;I'll update this post with the results. Fingers crossed this is the end of the saga! 🤞&lt;/p&gt;




&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Watch out for overrides:&lt;/strong&gt; &lt;code&gt;NSAllowsArbitraryLoadsInWebContent&lt;/code&gt; silently disables &lt;code&gt;NSAllowsArbitraryLoads&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Know your keys:&lt;/strong&gt; &lt;code&gt;InsecureHTTPLoads&lt;/code&gt; allows HTTP connections, but &lt;strong&gt;&lt;code&gt;NSExceptionMinimumTLSVersion&lt;/code&gt;&lt;/strong&gt; is required for TLS version issues.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Be specific:&lt;/strong&gt; Don't disable security globally. Configure exceptions only for the domains that actually need them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;References &lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW35" rel="noopener noreferrer"&gt;https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW35&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>networking</category>
      <category>ssl</category>
      <category>tls</category>
    </item>
  </channel>
</rss>
