Quick answer: iOS Safari is the most restrictive WebGL host in common use. Reduce your memory footprint below 384 MB, enable Brotli compression, gate all audio behind user interaction, and enable WebGL 1.0 fallback for older iPhones. Connect your iPhone to a Mac with Safari Web Inspector enabled to see the actual console error — that tells you which of the three common causes is yours.
Your Unity WebGL build works on Chrome, Firefox, Edge, and even desktop Safari. You test it in Responsive Design Mode on your Mac and it looks great at iPhone resolutions. Then a real iPhone loads it and crashes to a blank page before the splash finishes. Welcome to iOS Safari — the strictest WebGL implementation that millions of players use daily. Here are the three most common causes and how to fix each.
Step 1: See the Actual Error
Before guessing, find out what’s crashing. On a Mac, open Safari, go to Settings > Advanced, and enable “Show Develop menu in menu bar.” On the iPhone, go to Settings > Safari > Advanced and enable Web Inspector. Connect the iPhone with a cable, launch your game in iPhone Safari, then on the Mac open Develop > [Your iPhone] > [Your Game Page]. The Web Inspector opens and you can see the console output in real time.
Common errors you’ll see:
Out of memoryorWebAssembly.Memory allocation failed— you’re asking for more memory than iOS Safari will give you.WebGL: INVALID_OPERATION: texImage2D: no textureor similar — a shader or texture format isn’t supported on the iOS GPU.NotAllowedError: The request is not allowed by the user agent— audio autoplay was blocked.RangeError: Maximum call stack size exceeded— JavaScriptCore has a smaller stack than V8.
Fix 1: Reduce Memory Footprint
The single most common cause of iOS Safari crashes in Unity WebGL is memory. iOS Safari enforces a roughly 300–500 MB limit on the WebGL heap, much smaller than desktop browsers. Unity’s default memory allocation is often too large for this.
In Player Settings > Publishing Settings, set Memory Size to 256 MB (or 384 if your game genuinely needs more and you can accept that some iPhones will still fail). Enable Code Optimization: Size, LTO to minimize the built-in engine footprint. Set Managed Stripping Level: High to strip all unused C# code.
// Editor script: configure WebGL build for iOS compatibility
using UnityEditor;
using UnityEngine;
public class WebGLIOSBuildSettings
{
[MenuItem("Build/Configure WebGL for iOS Safari")]
public static void Configure()
{
// Memory
PlayerSettings.WebGL.memorySize = 384;
// Compression
PlayerSettings.WebGL.compressionFormat = WebGLCompressionFormat.Brotli;
// Code stripping
PlayerSettings.SetManagedStrippingLevel(
NamedBuildTarget.WebGL, ManagedStrippingLevel.High);
// Code optimization
PlayerSettings.SetIl2CppCompilerConfiguration(
NamedBuildTarget.WebGL, Il2CppCompilerConfiguration.Master);
// Disable exception support (smallest footprint)
PlayerSettings.WebGL.exceptionSupport = WebGLExceptionSupport.None;
// Enable WebGL 1 fallback for older iOS
PlayerSettings.WebGL.linkerTarget = WebGLLinkerTarget.Wasm;
Debug.Log("WebGL build configured for iOS Safari");
}
}
Also audit your assets. A single 4K uncompressed texture can be 64 MB on its own. Use ASTC compression (supported on iOS) and downscale textures for web to at most 1024x1024 unless you have a specific reason for higher resolution. Compress audio to Vorbis with quality 0.5 or lower.
Fix 2: Handle WebGL Feature Gaps
iOS Safari supports WebGL 2 on iOS 15 and later, but the implementation has quirks. Certain shader features work on desktop WebGL 2 but fail on iOS WebGL 2, especially around floating-point textures, integer samplers, and compute shaders (which are unsupported entirely in WebGL).
The safest path for broad compatibility is to target the URP with the following shader-safe settings:
- Disable HDR rendering (32-bit float buffers are unreliable on iOS)
- Use 8-bit normal maps (not 16-bit)
- Avoid
texture3Dandisampler2D - Keep post-processing to bloom and tonemapping at most
- Disable anti-aliasing on mobile WebGL (it’s expensive and iOS GPUs don’t handle it well)
If a player is on iOS 14 or older (still a non-trivial user share in 2026 due to devices like the iPhone 6s), they need WebGL 1 fallback. In Player Settings, under WebGL, enable the WebGL 1.0 fallback option. Unity will produce both WebGL 1 and WebGL 2 builds and select automatically based on browser capabilities.
Fix 3: Gate Audio Behind User Interaction
iOS Safari blocks all audio playback until the user has interacted with the page. If your game tries to play a sound (even silently) before the player taps, iOS throws a NotAllowedError that can cascade into a full game crash depending on how Unity’s audio system handles it.
Wrap your audio initialization behind a click handler. Show a “Tap to Start” screen that requires interaction before calling any AudioSource.Play or initializing the Web Audio context:
using UnityEngine;
public class AudioGateForIOS : MonoBehaviour
{
[SerializeField] private GameObject tapToStartOverlay;
[SerializeField] private AudioListener audioListener;
private bool audioEnabled = false;
void Start()
{
if (Application.platform == RuntimePlatform.WebGLPlayer)
{
audioListener.enabled = false;
tapToStartOverlay.SetActive(true);
}
else
{
EnableAudio();
}
}
public void OnTapToStart()
{
tapToStartOverlay.SetActive(false);
EnableAudio();
}
void EnableAudio()
{
audioListener.enabled = true;
audioEnabled = true;
// Now safe to start background music, etc.
}
}
Don’t just rely on the AudioListener; if you have code paths that play one-shot sounds during loading, they’ll throw exceptions before the listener is enabled. Guard those calls with if (audioEnabled) checks and log the attempts so you can find any stragglers.
Testing on Real Devices
The iOS Simulator is not a substitute for a real iPhone. The simulator runs on your Mac’s hardware and memory, so it won’t hit the memory limits. Use a real iPhone for every test, ideally one from the last three generations to cover the performance envelope your players are on.
BrowserStack and Sauce Labs both offer real iOS device testing if you don’t own a fleet. For indie studios, buying a used iPhone SE (2nd gen) for $100 covers the “lowest reasonable device” case and catches 80% of iOS-specific issues.
Capturing Crash Metadata From iOS Players
iOS Safari doesn’t give you easy access to crash information after the fact — when the tab crashes, the entire page dies. You need to catch the error before the crash completes. Wrap Unity’s onerror handler in your HTML template to capture errors and send them to your bug tracker before the browser kills the tab:
<script>
window.addEventListener('error', function(e) {
// Fire-and-forget crash report to your backend
navigator.sendBeacon('/api/crash', JSON.stringify({
message: e.message,
file: e.filename,
line: e.lineno,
stack: e.error && e.error.stack,
ua: navigator.userAgent,
memory: performance.memory && performance.memory.usedJSHeapSize
}));
});
</script>
navigator.sendBeacon is specifically designed to fire HTTP requests that survive page unload, making it perfect for last-gasp crash reporting. Use it instead of fetch which may get cancelled when the page dies.
Related Issues
For general HTML5 crash reporting, see Setting Up Crash Reporting for HTML5 Browser Games. For Unity-specific crash debugging, check Understanding Stack Traces in Unity Crash Logs. For more WebGL build debugging, see Bug Reporting for HTML5 and Web Games with Bugnet.
iOS Safari is the canary in your WebGL coal mine. If it works there, it works everywhere.