<?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>DEV Community: raykim</title>
    <description>The latest articles on DEV Community by raykim (@raykim2414).</description>
    <link>https://dev.to/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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3700633%2F9fd6b79b-2d55-43a5-a9de-74868b8bdade.png</url>
      <title>DEV Community: raykim</title>
      <link>https://dev.to/raykim2414</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/raykim2414"/>
    <language>en</language>
    <item>
      <title>[iOS] Why Passthrough MP4 Export Failed for iPhone Videos</title>
      <dc:creator>raykim</dc:creator>
      <pubDate>Wed, 03 Jun 2026 00:07:12 +0000</pubDate>
      <link>https://dev.to/raykim2414/ios-why-passthrough-mp4-export-failed-for-iphone-videos-268f</link>
      <guid>https://dev.to/raykim2414/ios-why-passthrough-mp4-export-failed-for-iphone-videos-268f</guid>
      <description>&lt;p&gt;A user picks a video from Photos. The app turns that video into a file the server can accept. Then it uploads the file.&lt;/p&gt;

&lt;p&gt;On the surface, this sounds like a simple upload flow.&lt;/p&gt;

&lt;p&gt;In practice, iOS makes you answer a much more specific question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do you reliably turn a video from the user's Photo Library into an uploadable MP4 file?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At first, I used &lt;code&gt;AVAssetExportPresetPassthrough&lt;/code&gt;. It looked like the right default. It preserves the original media as much as possible, avoids unnecessary re-encoding, and is fast when it works. No quality loss, less CPU usage, less battery cost.&lt;/p&gt;

&lt;p&gt;But some videos failed during export. The error was usually in the &lt;code&gt;AVFoundationErrorDomain Code=-11838&lt;/code&gt; family.&lt;/p&gt;

&lt;p&gt;Apple describes this error as &lt;code&gt;operationNotSupportedForAsset&lt;/code&gt;: an operation was attempted that is not supported for the asset.&lt;/p&gt;

&lt;p&gt;At first, I suspected an iCloud download issue, a Photos permission edge case, or something retryable inside &lt;code&gt;PHImageManager&lt;/code&gt;. That was not the real problem.&lt;/p&gt;

&lt;p&gt;The actual problem was more fundamental:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I was trying to write an iPhone MOV asset into an MP4 file using a passthrough export.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Original Flow
&lt;/h2&gt;

&lt;p&gt;The original code was roughly shaped like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;PHImageManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestExportSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;forVideo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;exportPreset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AVAssetExportPresetPassthrough&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;exportSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="n"&gt;exportSession&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outputURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;outputURL&lt;/span&gt;
    &lt;span class="n"&gt;exportSession&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outputFileType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mp4&lt;/span&gt;
    &lt;span class="n"&gt;exportSession&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exportAsynchronously&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// upload&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intention was reasonable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ask Photos for an export session for the &lt;code&gt;PHAsset&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;AVAssetExportPresetPassthrough&lt;/code&gt; to preserve the original quality.&lt;/li&gt;
&lt;li&gt;Write the result as an &lt;code&gt;.mp4&lt;/code&gt; file for upload.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Many videos exported successfully this way. That is what made the bug confusing. Some videos worked. Some did not.&lt;/p&gt;

&lt;p&gt;The hidden assumption was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Any iPhone video can become an MP4 file through a passthrough export.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That assumption is not always true.&lt;/p&gt;

&lt;h2&gt;
  
  
  iPhone Videos Are Usually MOV Files
&lt;/h2&gt;

&lt;p&gt;To a user, it is just "a video." Internally, an iPhone camera video is commonly stored as a QuickTime Movie container, or &lt;code&gt;.mov&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Apple documents the QuickTime Movie type as &lt;code&gt;com.apple.quicktime-movie&lt;/code&gt;, commonly using the &lt;code&gt;.mov&lt;/code&gt; or &lt;code&gt;.qt&lt;/code&gt; extension. MP4, on the other hand, is an MPEG-4 movie type. &lt;code&gt;UTType.mpeg4Movie&lt;/code&gt; uses the identifier &lt;code&gt;public.mpeg-4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Both are video containers, but they are not the same file type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MOV
- QuickTime Movie container
- Common in Apple's media ecosystem
- Usually uses the .mov extension
- Can contain QuickTime-specific metadata and track structures

MP4
- MPEG-4 based container
- Common across web, servers, Android, browsers, and CDNs
- Usually uses the .mp4 extension
- Broadly compatible, but still has its own container rules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MOV and MP4 are structurally related. Some MOV files can be moved into MP4 without re-encoding.&lt;/p&gt;

&lt;p&gt;But "similar" does not mean "always interchangeable."&lt;/p&gt;

&lt;h2&gt;
  
  
  Container and Codec Are Different Things
&lt;/h2&gt;

&lt;p&gt;The easiest way to get confused here is to mix up container and codec.&lt;/p&gt;

&lt;p&gt;A container is the file wrapper. MOV, MP4, and MKV are containers.&lt;/p&gt;

&lt;p&gt;A codec is how the actual video or audio data is encoded. H.264, HEVC, ProRes, and AAC are codecs.&lt;/p&gt;

&lt;p&gt;An iPhone video can look like any of these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MOV + H.264 + AAC
MOV + HEVC(H.265) + AAC
MOV + HEVC 10-bit HDR + AAC
MOV + Dolby Vision HDR metadata
MOV + Apple ProRes
MOV + Cinematic mode metadata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So it is not enough to ask whether the file is MOV or MP4. You also need to care about the video codec, audio codec, HDR metadata, edit information, QuickTime metadata, and the actual track layout inside the asset.&lt;/p&gt;

&lt;p&gt;Apple explains that High Efficiency media on iPhone and iPad uses HEVC, also known as H.265, for video. If the camera is set to Most Compatible, new videos use H.264 instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;High Efficiency
- video: HEVC / H.265

Most Compatible
- video: H.264
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means "an iPhone video" is not one single format.&lt;/p&gt;

&lt;p&gt;One video might be H.264 SDR. Another might be HEVC. Another might be HEVC 10-bit HDR or Dolby Vision. On Pro devices, it might even be ProRes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passthrough Is Not a Converter
&lt;/h2&gt;

&lt;p&gt;The name &lt;code&gt;AVAssetExportPresetPassthrough&lt;/code&gt; is accurate.&lt;/p&gt;

&lt;p&gt;It tries to export the asset close to its current format. It is not a general-purpose "make this compatible with MP4" converter.&lt;/p&gt;

&lt;p&gt;Passthrough tries to preserve the original tracks when possible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;passthrough
- Does not re-encode the video track
- Tries to preserve the audio track
- Fast
- Minimal or no quality loss
- Requires the original track structure to be compatible with the output container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes this combination stronger than it first appears:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="nv"&gt;exportPreset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AVAssetExportPresetPassthrough&lt;/span&gt;
&lt;span class="n"&gt;outputFileType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mp4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is effectively asking AVFoundation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Take the video, audio, and metadata from this original MOV asset, preserve them as much as possible, and write them into an MP4 file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some MOV files can satisfy that request.&lt;/p&gt;

&lt;p&gt;Some cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cases That Can Cause Trouble
&lt;/h2&gt;

&lt;p&gt;The following cases are not guaranteed to fail. They are just the kinds of assets that make &lt;code&gt;passthrough + .mp4&lt;/code&gt; a risky assumption.&lt;/p&gt;

&lt;h3&gt;
  
  
  HEVC / H.265
&lt;/h3&gt;

&lt;p&gt;HEVC itself is not the problem. HEVC can exist inside MP4.&lt;/p&gt;

&lt;p&gt;But real compatibility depends on more than the codec name. Profile, level, bit depth, codec tag, color information, OS version, and the export session's supported file types all matter.&lt;/p&gt;

&lt;p&gt;Copying an original HEVC track into MP4 is not the same thing as re-encoding that video into an MP4-friendly output.&lt;/p&gt;

&lt;h3&gt;
  
  
  HDR / Dolby Vision
&lt;/h3&gt;

&lt;p&gt;Supported iPhone models can record Dolby Vision HDR video.&lt;/p&gt;

&lt;p&gt;HDR video carries more information than a normal SDR video: HEVC 10-bit data, HLG or Dolby Vision metadata, color space information, and related signaling.&lt;/p&gt;

&lt;p&gt;Those details make sense in Apple's Photos and MOV ecosystem. But if you ask AVFoundation to preserve them and write an MP4 file through passthrough, the specific combination still has to be supported.&lt;/p&gt;

&lt;p&gt;If it is not supported, export fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apple ProRes
&lt;/h3&gt;

&lt;p&gt;ProRes is a high-quality codec intended for editing workflows.&lt;/p&gt;

&lt;p&gt;A ProRes source is very different from the small, widely compatible MP4 file an upload backend usually expects. Passthrough does not turn ProRes into H.264 or HEVC.&lt;/p&gt;

&lt;p&gt;If the server needs a normal MP4 file, ProRes may need to be transcoded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cinematic Mode
&lt;/h3&gt;

&lt;p&gt;Cinematic mode is not just a visual effect baked into a video.&lt;/p&gt;

&lt;p&gt;It allows focus points and depth-of-field effects to be changed after capture. Apple also exposes QuickTime metadata related to Cinematic Video intent in AVFoundation.&lt;/p&gt;

&lt;p&gt;That metadata is meaningful in Apple's MOV and Photos ecosystem. It does not mean the same information can always be preserved into an upload-oriented MP4 file through passthrough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Slo-mo, Time-lapse, and Edited Videos
&lt;/h3&gt;

&lt;p&gt;Slow-motion, time-lapse, and edited videos can be more complex than a single plain video track.&lt;/p&gt;

&lt;p&gt;Time mapping, current versus original versions, audio mixes, and edit metadata can all be involved.&lt;/p&gt;

&lt;p&gt;The video may play perfectly in Photos, but still fail for a specific export preset and output file type combination.&lt;/p&gt;

&lt;h3&gt;
  
  
  MOV Files Saved by Other Apps
&lt;/h3&gt;

&lt;p&gt;Videos in the Photo Library are not always captured by Apple's Camera app.&lt;/p&gt;

&lt;p&gt;They may be saved by other apps, downloaded from the web, or produced by editing tools. These files can have more varied codecs, audio formats, metadata, and track layouts.&lt;/p&gt;

&lt;p&gt;Assuming every one of them can be passed through into MP4 is risky.&lt;/p&gt;

&lt;h2&gt;
  
  
  So It Was Not Just a Codec Problem
&lt;/h2&gt;

&lt;p&gt;It is tempting to ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this failing because it is HEVC?&lt;/li&gt;
&lt;li&gt;Is it failing because it is MOV?&lt;/li&gt;
&lt;li&gt;Is iCloud failing to download the original?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The more accurate explanation is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The original MOV asset contained a combination of tracks and metadata that could not be written into an MP4 container without re-encoding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the problem was not one codec by itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Possible contributors
- MOV container
- HEVC, HDR, ProRes, or codec profile
- QuickTime metadata
- Cinematic/depth/focus metadata
- Edit information
- Photos current asset representation
- MP4 outputFileType
- passthrough export preset

Actual failure
- This combination could not be written as MP4 through passthrough
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is why &lt;code&gt;AVFoundationErrorDomain Code=-11838&lt;/code&gt; was an important clue.&lt;/p&gt;

&lt;p&gt;It did not mean "try again later." It was closer to "this operation is not supported for this asset."&lt;/p&gt;

&lt;p&gt;Repeating the same request was not a real fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remux vs Transcode
&lt;/h2&gt;

&lt;p&gt;This issue becomes easier to reason about if you separate remuxing from transcoding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;remux
- Keeps the original video/audio data
- Changes only the container
- Can turn MOV into MP4 quickly
- No quality loss
- Works only if the original tracks are compatible with the target container

transcode
- Re-encodes video and/or audio
- Creates new streams suitable for the target container
- Slower and more CPU intensive
- May change quality or file size
- More compatible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AVAssetExportPresetPassthrough&lt;/code&gt; is closer to remuxing.&lt;/p&gt;

&lt;p&gt;An export preset such as &lt;code&gt;AVAssetExportPreset1920x1080&lt;/code&gt; is closer to transcoding. It can create a new output that is more suitable for MP4 upload.&lt;/p&gt;

&lt;p&gt;If your goal is to preserve the original media, passthrough is a good first attempt.&lt;/p&gt;

&lt;p&gt;If your goal is to produce an uploadable MP4 file for a backend and other clients, passthrough alone is not enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;The fix was not to blindly retry the same export.&lt;/p&gt;

&lt;p&gt;The fix was to use a fallback pipeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. passthrough + mp4
   - Fastest path when it works.
   - Minimal quality loss.
   - Use it when the original tracks are compatible with MP4.

2. 1920x1080 + mp4
   - If passthrough is unsupported, switch to re-encoding.
   - Do not try to preserve the original MOV structure.
   - Produce a more upload-friendly MP4 file.

3. requestAVAsset + export
   - If the Photos requestExportSession path fails, request the AVAsset directly.
   - Build another export path from the requested asset.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that passthrough was not removed.&lt;/p&gt;

&lt;p&gt;It is still the best first path when it works. The mistake was treating it as the only path.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;The main lesson was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"An iPhone video" is not one format.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every selected video may look like &lt;code&gt;PHAsset.mediaType == .video&lt;/code&gt;, but internally it can involve MOV, H.264, HEVC, HDR, ProRes, Cinematic metadata, edits, and iCloud state.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AVAssetExportPresetPassthrough&lt;/code&gt; does not normalize all of that into a standard MP4 file. It preserves the original when possible.&lt;/p&gt;

&lt;p&gt;The right question for the upload pipeline was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Are we trying to preserve the original, or are we trying to produce a reliably uploadable file?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this case, the answer was both.&lt;/p&gt;

&lt;p&gt;Try to preserve the original first. If that is not supported, fall back to a transcoding path.&lt;/p&gt;

&lt;p&gt;That turned the issue from "retry a failed export" into "switch export strategies when the current strategy is not supported."&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/avfoundation/avassetexportpresetpassthrough" rel="noopener noreferrer"&gt;Apple Developer Documentation: AVAssetExportPresetPassthrough&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/photos/phimagemanager/requestexportsession%28forvideo%3Aoptions%3Aexportpreset%3Aresulthandler%3A%29" rel="noopener noreferrer"&gt;Apple Developer Documentation: PHImageManager.requestExportSession&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/avfoundation/averror-swift.struct/code/operationnotsupportedforasset" rel="noopener noreferrer"&gt;Apple Developer Documentation: AVError.operationNotSupportedForAsset&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/avfoundation/avfiletype/mov?language=objc" rel="noopener noreferrer"&gt;Apple Developer Documentation: AVFileTypeQuickTimeMovie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/mpeg4movie?changes=_1" rel="noopener noreferrer"&gt;Apple Developer Documentation: UTType.mpeg4Movie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.apple.com/en-us/116944" rel="noopener noreferrer"&gt;Apple Support: Using HEIF or HEVC media on Apple devices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.apple.com/guide/iphone/adjust-hdr-camera-settings-iph2cafe2ebc/ios" rel="noopener noreferrer"&gt;Apple Support: Adjust HDR camera settings on iPhone&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.apple.com/en-us/109041" rel="noopener noreferrer"&gt;Apple Support: About Apple ProRes on iPhone&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.apple.com/guide/iphone/record-video-in-cinematic-mode-ipha0706e2bc/ios" rel="noopener noreferrer"&gt;Apple Support: Record video in Cinematic mode with your iPhone camera&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>avfoundation</category>
      <category>debugging</category>
    </item>
    <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://dev.to/raykim2414/ios-debugging-ssl-handshake-failures-3jf0</link>
      <guid>https://dev.to/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>
