Your Universal Link opens Safari instead of the app. Nine times out of ten, the culprit is not your code; it is a malformed or mis-hosted apple-app-site-association file. It is the single most common Universal Links failure we see across deep linking audits, and it is almost always a small, fixable detail in one JSON file.
This is the file-level guide: how to structure the AASA file, where to host it, and how to validate it so links open the app every time. For why links break once they are inside Instagram or TikTok, that is a separate problem covered in our in-app browser guide.
What is an AASA file, and why do Universal Links depend on it?
An apple-app-site-association (AASA) file is a JSON file hosted on your domain that tells iOS which app is allowed to open which URLs on that domain. Without a valid one, iOS has no reason to hand a link to your app, so it opens the URL in Safari instead.
When your app is installed, iOS fetches the AASA file for any domain listed in the app's associated-domains entitlement, then uses it to decide whether a tapped link should deep link into the app. If the file is missing, unreachable, or malformed, the association silently fails and every link falls back to the browser. For where Universal Links sit among the broader link types, see our complete guide to deep links.
The anatomy of a correct AASA file
The file lives under the applinks key. A minimal modern version looks like this:
{
"applinks": {
"details": [
{
"appIDs": ["TEAMID.com.yourcompany.app"],
"components": [
{ "/": "/promo/*", "comment": "Open promo links in app" },
{ "/": "/blog/*", "exclude": true }
]
}
]
}
}
The details that matter:
- appID format is
TeamID.bundleID. The Team ID is your 10-character Apple Developer team identifier, not the app name. A wrong Team ID is a frequent cause of silent failure. - Use
components(newer) orpaths(legacy).componentsallows finer matching and exclusions;pathsstill works. Do not mix the two carelessly. - Path matching is literal and order-sensitive. Wildcards (
*) and exclusions behave exactly as written, so an over-broad*can route URLs you meant to keep in the browser.
Tech Explainer: why the JSON itself breaks things. iOS parses this file strictly. A trailing comma, a .json extension that changes how your server sets the content-type, or BOM characters from a text editor can all make the file unparseable. The link then fails with no error shown to the user. Validate the raw bytes, not just how it looks in a browser tab.
Hosting requirements that trip teams up
Getting the file right is half the job; serving it correctly is the other half:
- Host at
https://yourdomain/.well-known/apple-app-site-association. HTTPS is mandatory. The.well-knownpath is the expected location. - Serve it with no redirect. If the request 301s or 302s to another URL, iOS treats the association as invalid. Serve the file directly with a 200 response.
- Use the right content-type and no extension. The file should have no
.jsonextension and be served asapplication/json. A misconfigured server is a classic break point. - Add the associated-domains entitlement in the app. The domain must be declared on the app side (for example
applinks:yourdomain) or iOS never fetches the file at all.
For branded or subdomain hosting (DNS, SSL, and where the file sits), our custom link domains guide covers the domain side in full. Apple's own associated-domains documentation is the authoritative reference for the entitlement.
How to validate and debug an AASA file
Most AASA problems show the same handful of signatures. Diagnose in this order:
- Confirm the file is served correctly. Request the
.well-knownURL directly and check for a 200,application/json, no redirect, and valid JSON. - Test on a real device, not the simulator. Universal Links behave differently in the simulator; a clean install on a physical device is the only reliable test.
- Read the failure signature:
- Opens Safari every time: the file is unreachable, redirected, or the entitlement is missing.
- Opens the app but the wrong screen: the file is valid but your path matching or in-app routing is off.
- Works on one iOS version only: stale entitlement or a caching issue on older builds.
The cache gotcha most guides skip: Apple fetches the AASA file through a CDN at install time and caches it. Editing the file does not take effect on already-installed apps immediately, and a fresh install pulls Apple's cached copy, not your edit, until it refreshes. Plan changes ahead of campaigns rather than minutes before. For a fuller iOS failure-mode tree, see why your deep links break on iOS.
AASA and attribution: keeping context through the open
A correct AASA file is necessary for Universal Links, but it is not sufficient for measurement:
- The file gets the user into the app. It does nothing on its own to preserve the campaign context or attribute the open.
- To carry context (which campaign, which creative, which destination) through the tap and, if needed, through an install, you pair Universal Links with deferred deep linking and attribution, as in Linkrunner's deferred deep linking setup.
- Android parity: the equivalent file for Android App Links is
assetlinks.json, hosted at the same.well-knownpath with the same no-redirect, correct-content-type discipline.
A pre-launch AASA checklist
Before any campaign that relies on Universal Links goes live:
- The
.well-known/apple-app-site-associationURL returns 200,application/json, no redirect. - The appID uses the correct
TeamID.bundleID. - Path components match exactly the URLs you intend to open in-app, with deliberate exclusions.
- The associated-domains entitlement is present in the shipped build.
- You have tested a real-device install, and allowed time for Apple's cache after any edit.
Re-run the full list after any domain change, Team ID change, or app rebuild. Our deep link QA checklist folds this into a wider pre-launch routine. Platforms like Linkrunner host and maintain the AASA file on a branded subdomain so teams do not hand-edit it before every change.
Frequently asked questions
Where do I host the apple-app-site-association file?
At https://yourdomain/.well-known/apple-app-site-association, served over HTTPS, with a 200 response and no redirect.
Why are my Universal Links opening Safari instead of the app?
Almost always because iOS could not validate the association: the file is unreachable, redirected, served with the wrong content-type, has a malformed appID, or the associated-domains entitlement is missing from the app.
Does the AASA file need a .json extension or a specific content-type?
No extension. Serve it as application/json. An incorrect content-type or an added .json extension is a common reason iOS rejects the file.
How long do AASA file changes take to propagate?
Not instantly. Apple fetches the file through a cached CDN at install time, so edits may not reach already-installed apps or fresh installs immediately. Make changes ahead of launches, not at the last minute.
The takeaway
Universal Links almost never fail because of clever code; they fail because of a small detail in one JSON file: a wrong Team ID, a stray redirect, the wrong content-type, a missing entitlement, or Apple's cache not yet refreshed. Get the structure right, host it strictly, test on a real device, and respect the cache, and links will open the app reliably. Then pair the file with deferred deep linking so the open is also measured.
If you would rather not hand-maintain the AASA file and its caching quirks, request a demo from Linkrunner and run the pre-launch checklist above against your current setup first.
