iOS · Universal Links
Universal Links not working? Your apple-app-site-association is probably redirecting
You tapped a link to your own site, expecting your app to open. Safari opened instead — no crash, no error, no log line. If you have spent an afternoon staring at a file that looks completely correct, there is a good chance the file is fine and the way your server delivers it is the problem.
The single most common cause of broken iOS Universal Links is a redirect sitting in front of your apple-app-site-association (AASA) file. Apple fetches it without following that redirect — so unless Apple's CDN already holds a valid cached copy, your association silently fails.
The symptom
- Tapping a link to your domain opens Safari, not your app.
- It sometimes works on one build/device and not another (stale association cache).
- Long-pressing the link and choosing Open in “YourApp” is missing.
- It worked in development, then broke after a DNS, CDN, or hosting change.
30-second diagnosis
iOS expects your file at exactly this URL, over HTTPS, returned directly:
https://yourdomain.com/.well-known/apple-app-site-associationFetch it the way Apple does — and crucially, without letting curl follow redirects:
curl -sS -D - -o /dev/null \
https://yourdomain.com/.well-known/apple-app-site-associationRead the very first status line:
HTTP/2 200— good, the file is served directly. Check its contents next.HTTP/2 301/302/307/308— this is your bug. Apple sees a redirect and treats the file as missing. Note we did not pass-L; adding it makes curl follow the redirect and hides the exact problem you are hunting.
Why a redirect breaks everything
When you install or update an app that declares an applinks: Associated Domain, iOS asks Apple's CDN — not your origin server — for your AASA file. That service fetches the well-known URL and does not follow 3xx redirects. A 301 to the same file on www, an http → https upgrade, a trailing-slash normalization, a marketing-platform catch-all, an SSO wall that 302s anonymous requests — any of these returns a redirect status, and the association silently fails.
This is why the classic trap is apex ↔ www. You serve the app from example.com, but your host 301-redirects it to www.example.com. Your browser follows it and the page loads fine, so the redirect is invisible day to day. Apple's fetcher does not follow it, so Universal Links never associate.
The fix is not “add the file to the redirect target.” The file must be served directly, with a 200, at the exact host you list in your Associated Domains entitlement (applinks:example.com). If you support both apex and www, serve a valid 200 file on both hosts and list both domains.
The other failure modes (once the 200 is fixed)
A direct 200 is necessary but not sufficient. Then verify the file's contents:
It must be valid JSON, with no .json extension. The file is literally named apple-app-site-association — no extension. A parse failure shows as AASA_NOT_JSON.
Content-Type should be application/json. Modern iOS is lenient, but some proxies are not (AASA_WRONG_CONTENT_TYPE).
Your app ID must be listed. iOS reads app IDs from applinks.details[].appIDs. A minimal modern file:
{
"applinks": {
"details": [
{
"appIDs": ["ABCDE12345.com.yourcompany.yourapp"],
"components": [
{ "/": "/products/*", "comment": "Product pages" }
]
}
]
}
}The app ID is <TeamID>.<BundleID>. If applinks.details has no app IDs at all, Universal Links cannot work (AASA_NO_APPS). If the file is valid but your specific TeamID.BundleID is not in it, only your build fails (AASA_APPID_MISSING).
Keep it under 128 KB and don't gate it. Apple caps the file size (AASA_TOO_LARGE), and it must be publicly fetchable with no auth, no geo-block, and no bot challenge.
The subtle one: Apple's CDN cache and origin divergence
Because iOS reads your file through Apple's CDN, two things burn people:
- Changes are not instant. Apple caches the association aggressively — often around a day. A device that cached the “missing” result may keep failing until the cache expires or the app is reinstalled. Don't conclude your fix failed after 5 minutes.
- Your origin and Apple's CDN can disagree. If you fix the file at origin but a CDN/edge layer still serves a stale or redirecting copy, your own
curlto origin looks perfect while Apple still sees the broken version. This is theAASA_CDN_DIVERGENTcase — the only reliable way to catch it is to fetch both the origin and the Apple-CDN copy and compare them. - The CDN can also mask a redirect. Sometimes the opposite happens: your origin redirects, yet Apple's CDN already holds a valid copy (it resolved your
www/registered host), so Universal Links keep working despite the misconfiguration. That's why this tool checks Apple's CDN as the source of truth and reports a redirecting-but-CDN-valid domain as a warning (working today, but fragile) rather than a hard failure — relying on it is unsupported, and the next cache refresh can still break you.
Fix checklist
- The well-known URL returns 200 (no
-L, no 3xx). - Same for every host you list in
applinks:(apex andwww). - Body is valid JSON, file has no extension, served as
application/json. applinks.details[].appIDscontains your exact TeamID.BundleID.- File is public (no redirect, no auth wall, no bot challenge) and < 128 KB.
- After fixing, reinstall the app or wait out Apple's cache before re-testing.