
You built a share flow, sent a link to a tester, and it opened in Safari instead of your app. The AASA file looks correct. The entitlement looks correct. Nothing works, and the error is silent.
Universal links break when trust fails between your app, your domain, and the Apple CDN. This article shows how iOS resolves that trust, how to configure it correctly, and how to debug the failures that waste the most time. The major architectural change happened in iOS 14, when Apple moved AASA resolution to a CDN. Every setup and debugging decision since then flows from that design choice. Apple continues to refine associated domains guidance in newer WWDC sessions, but the core resolution model has held.
What universal links are and how iOS resolves them
Universal links open your app only when iOS trusts both your domain and your app. That trust depends on a valid AASA file, a matching entitlement, and a cached decision delivered through the Apple CDN.
Universal links are standard HTTPS URLs. iOS intercepts them and routes them to your native app instead of Safari. They require a verified trust relationship between your app and your web domain. When the app is not installed, the same URL opens as a normal web page in Safari.
Two components create this trust relationship: an Apple App Site Association file, the AASA reference, on your server, and an associated domains entitlement in your app.
CDN-based resolution since iOS 14
The Apple CDN sits between your server and the device. That changes setup, updates, and debugging in three concrete ways:
- Your users' devices do not fetch the AASA file from your server directly
- The CDN, not your origin server, controls when devices see updates
- There is no manual invalidation mechanism for the cached file
Here is what actually happens at install time and afterward:
- The Apple CDN requests the AASA file for each associated domain
- At app install, the device downloads the AASA from the Apple CDN
- After install, devices check for CDN-cached AASA updates on a delayed cadence
- Reinstalling the app forces a fresh device fetch since no CDN invalidation mechanism exists
You are not configuring universal links directly for users' devices. You are configuring them for the Apple CDN, and that distinction shapes every debugging decision that follows.
Why Apple wants you off custom URL schemes
Universal links fix the ownership and fallback problems that custom URL schemes never solved. That security model explains why the setup rules are stricter.
The CDN-based trust model exists because the older alternative, custom URL schemes, offered no ownership verification. Custom URL schemes (myapp://path) let any app register any scheme with no proof of identity. Apple guidance is direct: custom URL schemes are insecure for sensitive data and are not suitable for authentication in new builds, with Apple recommending alternatives such as universal links and ASWebAuthenticationSession.
Universal links solve three problems custom schemes can not:
- Security. No other app can claim your universal link URL. The domain ownership verification at install time prevents hijacking.
- Fallback. When the app is not installed, the URL opens a real web page. Custom schemes fail silently.
- Privacy. Custom schemes allow apps to probe which other apps are installed via
canOpenURL. Universal links remove that vector.
Android app links work on similar principles, using an assetlinks.json file with SHA-256 fingerprint binding. Android app links also support flexible routing configurations. iOS has no documented equivalent. Path changes require an AASA update propagated through the Apple CDN, and that update may take time to reach devices.
Setting up the AASA file and associated domains
Universal links usually fail when the server-side AASA and the app entitlement do not match exactly. Check those two places before you debug anything else.
The security model and fallback behavior only work if both sides of the configuration, server and app, agree exactly. Getting universal links working requires the AASA file on your web server and the associated domains entitlement in Xcode to reference the same values. A mismatch between the app entitlement and the site association file is a common setup failure.
AASA file requirements
The AASA file defines which app can claim which URLs on your domain. Small delivery mistakes here often cause failures later. Three delivery rules matter most:
- The file must be named
apple-app-site-associationwith no extension - iOS checks both
https://example.com/.well-known/apple-app-site-associationand the root path, and your server must return HTTP 200 with valid JSON - No redirects are permitted, and the file should be publicly accessible without IP-based access restrictions
The domain must be accessible to the Apple CDN, which uses bot-style user-agents.
Use the modern AASA format with appIDs (array) and components:
{
"applinks": {
"details": [
{
"appIDs": ["ABCDE12345.com.example.app"],
"components": [
{ "/": "/buy/", "comment": "Product purchase flows" },
{ "/": "/help/website/", "exclude": true, "comment": "Web-only content" },
{ "/": "/help/", "?": { "articleNumber": "????" }, "comment": "Help articles with 4-char param" }
]
}
]
}
}Each appIDs entry follows the format <TeamID>.<BundleID>. The components array can match on path (/), query parameters (?), and fragment (#) together. Paths with no web equivalent should be explicitly excluded to prevent broken fallback experiences.
Associated domains entitlement
The entitlement tells iOS which domains the app is allowed to claim. If the entitlement does not match the domain setup, links fall back to Safari.
In Xcode, add the associated domains capability to your app target, then add entries in this format:
applinks:example.com
applinks:www.example.com
applinks:*.example.comThree rules cause failures when broken:
- Do not include
https://, path components, or trailing slashes *.example.comcovers subdomains only; addapplinks:example.comseparately for the root- Port numbers are not supported
If you keep those three rules intact, entitlement mismatches become easier to rule out. These rules are easy to miss because Xcode does not surface a clear error when you get them wrong.
For development against a private server, append ?mode=developer to bypass the CDN. This only works with development-signed builds, and it does not work with TestFlight, Ad Hoc, or App Store builds.
5 debugging failures that waste the most time
Most broken universal links come from a small set of delivery and caching problems. If setup looks right, check these failures first.
A configuration that looks correct in Xcode and on your server can still fail on a real device. Many universal link failures stem from AASA delivery or server and CDN configuration issues rather than app code. Apple documents additional ways to diagnose universal link and AASA issues, including checking device console logs, verifying the associated domains entitlement, confirming AASA file availability and server responses, and accounting for CDN caching behavior.
CDN caching with no invalidation
The Apple CDN can keep an older AASA version alive after you update your server. That makes a correct fix look broken.
Updating your AASA on the server does not propagate to installed apps immediately. Check what the Apple CDN currently has cached:
GET https://app-site-association.cdn-apple.com/a/v1/yourdomain.comIf the cached version is stale, try uninstalling and reinstalling the app on the test device. Note that this does not by itself force the Apple CDN to fetch a fresh apple-app-site-association file immediately.
WAFs like Cloudflare or AWS WAF may also block the CDN with a 403. TN3155 includes user-agent policies for the hosted AASA file. Test with:
curl -A "MyAgent-Bot/" https://example.com/.well-known/apple-app-site-associationFormat mixing between legacy and modern AASA
AASA parsing problems are easy to miss because the file may still look valid at a glance. Pick one format and validate it carefully.
The legacy format uses appID (singular string) and paths. The modern format uses appIDs (array) and components. Including both legacy (appID/paths) and modern (appIDs/components) fields in the same file is not a documented configuration. A single character mismatch in TEAM_ID.bundleID causes iOS to fall back to Safari. Always validate JSON before deploying.
Same-domain navigation suppression
A valid universal link does not fire in every context. Safari changes behavior based on how the user reaches the URL.
When a user taps a universal link, iOS can open the associated app directly rather than routing through the browser. Even if the path matches your AASA, same-domain navigation can suppress that jump. Route triggering links through a separate subdomain like app.example.com, with its own AASA file and entitlement entry. In practice, this means your marketing site at example.com links to app.example.com/product/123, and that subdomain hosts its own AASA and serves a fallback web page for users without the app.
Typing a URL directly into Safari's address bar also never opens the app. This is by design.
The developer mode trap
Developer mode can hide production behavior during testing. A build that worked in development may still fail in TestFlight.
Developers often validate with ?mode=developer during development and then distribute via TestFlight. That can expose CDN-dependent failures they have not tested against. TestFlight, Ad Hoc, and App Store builds all go through the Apple CDN.
Server redirect failures
Redirects break AASA delivery even when the final destination looks correct in a browser. This is common on domains with default hosting rules.
If applinks:yourdomain.com is your entitlement but your server redirects yourdomain.com to www.yourdomain.com, the AASA fetch fails. The CDN does not follow redirects. Apex-to-www redirects are especially common because many hosting providers configure them by default. Add both domains to your entitlement and serve the AASA file directly from each one.
Verify with:
curl -I https://yourdomain.com/.well-known/apple-app-site-associationExpected: HTTP 200, Content-Type: application/json, no Location: header.
What changed from iOS 17 through iOS 26
Most universal link behavior has stayed stable since iOS 14. Recent changes mostly affect debugging guidance and adjacent attribution features, not core resolution.
Once you have a working configuration and know the common failure points, the next question is whether recent iOS versions changed anything. Apple documents universal links behavior and debugging in TN3155, while significant platform changes are described elsewhere.
TN3155 revision timeline
The most useful recent change log lives in the debugging note rather than in a new universal links session. That makes the revision history worth checking when behavior feels inconsistent.
iOS 18 introduced AdAttributionKit re-engagement, which uses universal links as its deep linking transport. This adds an attribution layer on top of your existing configuration, and that layer does not change how universal links resolve.
iOS 26 AASA verification regression
Beta-specific issues can block release work even when your configuration is correct. Separate platform regressions from your own setup mistakes.
Developers report an AASA verification regression in iOS 26 beta. An Apple DTS engineer is engaged in the Apple developer forum. If you are shipping an app with universal links targeting iOS 26, monitor that thread until Apple publishes a resolution.
URL design and fallback best practices
A universal link still needs to work as a normal web URL. Good fallback pages protect the user experience when the app does not open.
Regardless of whether you configure universal links through Xcode or a hosted builder, URL design decisions determine what users without the app actually see. Every URL in your AASA must resolve to a functional web page. Not a 404, and not a redirect to your homepage. This page is what users without the app see. A May 2025 search engineering analysis confirms that deep links do not change search ranking. Google indexes the web page at the URL, not the app content.
Include a Smart App Banner on each fallback page:
<meta name="apple-itunes-app" content="app-id=YOUR_APP_STORE_ID">When a user opts out of opening your app via the Safari breadcrumb button, a Smart App Banner provides a re-engagement path for them to return to the app without visiting the App Store separately.
One final trap: handling navigation to your own universal-link URLs from within your app can be nuanced and may require app-specific testing. Use your internal router for in-app navigation.
The short version is simple. Universal links usually fail because the Apple CDN caches trust decisions, the AASA and entitlement do not match exactly, or the fallback URL behavior was not designed carefully. If you get those three parts right, setup and debugging become more predictable.
If you are building an iOS app and want to skip the infrastructure setup, try the Anything AI app builder. Its built-in authentication can reduce sign-in setup work. Its Stripe payments can help you charge users without wiring billing yourself. Its database support gives you a place to store app data without setting up that layer first.


