When Universal Links stop working, developers usually go hunting for bugs in their Swift code or their apple-app-site-association file. Both are worth checking. But the most common cause of silent failures is something most guides gloss over: the domain association verification process itself, and the CDN layer Apple uses to run it.
Understanding how Apple actually verifies that your domain is associated with your app, when that verification happens, and what can go wrong along the way will save you hours of debugging. This article covers the full picture.
What Domain Association Actually Means
A Universal Link only works because iOS trusts that the domain in the URL has explicitly authorized your app to handle it. That trust is established through the apple-app-site-association (AASA) file you host on your server (see our AASA file setup guide for the full walkthrough), combined with the Associated Domains entitlement baked into your app binary.
The entitlement says: "this app wants to handle links from example.com." The AASA file says: "links from example.com with these path patterns should open in app with team ID ABCDE12345." Apple's verification process checks that both sides agree. If they do not match, or if Apple cannot reach your AASA file, links fall back to Safari silently.
There is no error. No log entry the user sees. The link just opens a browser tab instead of your app.
Apple's CDN Layer
Before iOS 14, each user's device fetched your AASA file directly at install time. This created problems: slow installs, rate-limiting if your app was popular, and inconsistent behavior depending on network conditions at the moment of installation.
Starting with iOS 14, Apple changed the architecture. Devices no longer fetch your AASA file directly. Instead, Apple runs a content delivery network specifically for AASA files. Our article on Apple CDN validation for Universal Links covers the CDN behavior and caching rules in depth. When a device needs to verify domain association for an app, it queries Apple's CDN. The CDN has (or fetches) a cached copy of your AASA file and returns it.
Apple's CDN hostname is app-site-association.cdn-apple.com. You can query it yourself to see what Apple currently has cached for your domain:
curl -s "https://app-site-association.cdn-apple.com/apple-app-site-association?domain=yourdomain.com"
Replace yourdomain.com with your actual domain. The response is either the current cached AASA content, a 404 if Apple has not fetched it yet, or an error if something went wrong when Apple tried to reach your server.
This is one of the most useful debugging steps available to you. It shows exactly what Apple's infrastructure sees, which may differ from what you see when you curl your own server directly.
When Verification Happens
Apple's CDN does not fetch your AASA file continuously. There are specific triggers and a background refresh cycle.
At app install. When a user downloads your app from the App Store, iOS contacts Apple's CDN to retrieve the AASA files for every domain listed in your Associated Domains entitlement. This is the primary verification moment. If the CDN does not have a cached copy yet, it fetches from your server in real time.
At app update. Each time a new version of your app is installed, iOS re-verifies all associated domains. If you have changed your AASA file since the last version, the updated entitlements in the new binary trigger a fresh CDN lookup.
Periodic background refresh. Apple's CDN re-crawls AASA files on a rolling schedule. Apple does not publish the exact interval, but in practice it is roughly every 24 to 48 hours. This means changes to your AASA file do not take effect immediately for existing users. If you add a new path pattern or change app identifiers, installed users will get the update on the next background refresh, which could be up to two days later.
On demand during development. Developer mode bypasses the CDN entirely, which is covered below.
Checking What Apple Has Cached
Before assuming your AASA file is wrong, confirm what Apple's CDN is actually serving. There are two checks worth running.
First, verify your own server is returning the file correctly:
curl -I "https://yourdomain.com/.well-known/apple-app-site-association"
Look for:
- HTTP 200 status (not a redirect, not a 404)
Content-Type: application/json(Apple requires this;text/plainmay or may not work depending on the iOS version)- No redirect chains (Apple follows one redirect but some setups chain multiple)
Second, check Apple's CDN cache:
curl -s "https://app-site-association.cdn-apple.com/apple-app-site-association?domain=yourdomain.com" | python3 -m json.tool
If these two responses differ, you have a caching issue. The CDN may be serving a stale version of your file.
If Apple's CDN returns a 404 but your server returns a valid file, the CDN has not crawled your domain yet. This typically happens with new domains or apps that have never gone through App Store review. In this case, submitting a new build to App Store Connect (even a minor update) usually triggers a crawl.
What Causes Verification to Fail
Silent failures in Universal Links almost always trace back to a small set of root causes.
Wrong content type. Apple's CDN expects application/json. Some web servers default to application/octet-stream for files without extensions. Check your server configuration and set the content type explicitly.
HTTP instead of HTTPS. Your AASA file must be served over HTTPS with a valid TLS certificate. Self-signed certificates fail. Let's Encrypt certificates work fine, but the certificate must be valid at crawl time.
Redirect on the AASA path. If /.well-known/apple-app-site-association returns a 301 to https://www.yourdomain.com/.well-known/apple-app-site-association, that single redirect is fine. But if you have multiple hops or a redirect to a completely different domain, the crawl will fail.
Mismatched team ID or bundle ID. The appID field in your AASA file uses the format TEAMID.BUNDLEID. A single character off breaks association. Cross-check this against your Apple Developer account and your Xcode project settings.
Firewall blocking Apple's crawlers. Some CDNs and WAFs block unfamiliar user agents. Apple's CDN crawler uses a specific user agent string. If your infrastructure is blocking it, the crawl fails silently. You can check this by looking at your server access logs around the time a new app version was submitted to the App Store.
JSON syntax errors. An invalid JSON file causes the CDN to reject the content. Validate your AASA file before deploying:
cat apple-app-site-association | python3 -m json.tool
Multiple Domains and Subdomains
Apps frequently need to handle links from more than one domain: your main site, a marketing subdomain, a regional domain, or a short link domain. Each domain requires its own AASA file, and each must be listed as a separate entry in your Associated Domains entitlement.
In Xcode, Associated Domains entries look like this:
applinks:yourdomain.com
applinks:links.yourdomain.com
applinks:yourdomain.co.uk
Each entry maps to a separate AASA file hosted at that domain. Apple verifies each one independently. A failure on one domain does not affect the others.
Subdomains are not inherited. If your AASA file is at yourdomain.com, it does not automatically apply to links.yourdomain.com. You need a separate AASA file at each subdomain you want to use. Apple does not support wildcard domain entries in Associated Domains (a *.yourdomain.com entry is not valid).
One nuance worth knowing: the AASA file path (/.well-known/apple-app-site-association) must exist at the exact domain in the entitlement. If you list links.yourdomain.com in Associated Domains but only have the AASA file at yourdomain.com, verification fails.
If you are routing links through a platform like Tolinku, the domain association and AASA hosting are handled for you, which removes several points of failure. For self-managed setups, keeping track of AASA files across multiple domains adds operational overhead.
Invalidating the CDN Cache
You cannot directly purge Apple's CDN cache. There is no API or control panel for it. The cache expires on its own schedule, and the only reliable way to force a re-crawl is to trigger a new app install on a device (or submit a new app version to the App Store).
During development, this is painful. You update your AASA file, try a link on your test device, and nothing changes because the CDN still has the old version. Apple's developer mode exists specifically to address this.
Developer Mode: Bypassing the CDN
On iOS 16 and later, you can enable developer mode on a device through Settings > Privacy and Security > Developer Mode. When developer mode is on and your app is installed via Xcode (not TestFlight, not the App Store), iOS fetches AASA files directly from your server instead of through Apple's CDN.
This is the correct way to test AASA changes during development. Each time you install a fresh build via Xcode, iOS fetches your current AASA file directly. No caching delay.
A few notes on developer mode behavior:
- It only applies to devices with developer mode enabled and apps installed directly via Xcode.
- TestFlight builds still go through Apple's CDN, so they reflect the cached version, not your latest changes.
- Production App Store installs always use the CDN.
For a staging or QA workflow where you need to test AASA changes without going through a full App Store submission, the only practical option is installing directly via Xcode on developer-mode devices.
Watching Verification in Real Time
When you install your app via Xcode with developer mode on, you can watch the AASA fetch happen in real time using your server's access logs or a tool like Charles Proxy.
To confirm which AASA file your device fetched and from where, run this in Console.app on your Mac while your test device is connected:
- Open Console.app and select your connected device.
- Filter for
swcd(the daemon that handles associated domain verification on-device). - Install your app via Xcode.
- Watch the log output for entries showing the fetch request and result.
The swcd daemon logs are not always verbose, but they often surface "failed to fetch" errors that explain why verification is not working.
What Happens When Verification Fails
When Apple's CDN cannot verify domain association (missing file, wrong content type, network error, bad JSON), Universal Links for that domain stop working for that app. The behavior is a silent fallback: tapping a link that should open your app opens Safari instead. iOS does not display an error. The user sees a web page.
This fallback is by design. Universal Links are meant to degrade gracefully. If your app is not installed, the user gets the web page. If verification fails, the user gets the web page. From the user's perspective, the behavior is the same in both cases.
From a developer's perspective, this makes failures harder to catch without systematic testing. The best approach is to include Universal Link testing in your release checklist: install a fresh build from TestFlight on a clean device, tap a test link, and confirm the app opens. If it opens Safari, you have a domain association problem to investigate.
Putting It All Together
Universal Links domain association is not a one-time configuration step. It is an ongoing relationship between your server, Apple's CDN, and the entitlements in your app binary. Changes to any of these three components require re-verification, and that re-verification happens on Apple's schedule.
The practical checklist for keeping domain association working:
- Host your AASA file at
/.well-known/apple-app-site-associationover HTTPS withContent-Type: application/json. - Validate JSON syntax before deploying.
- Check Apple's CDN cache at
app-site-association.cdn-apple.comwhenever you make changes. - List every domain and subdomain separately in your Associated Domains entitlement.
- Test with Xcode installs on developer-mode devices to get immediate feedback.
- Account for 24-48 hour propagation delays when updating AASA files for production users.
- Watch your server access logs for Apple's crawler and make sure it is not being blocked.
For a broader overview that ties all of these pieces together, see our complete Universal Links guide. The Tolinku Universal Links developer guide covers the AASA file format, entitlement configuration, and Swift handling code in detail. The concepts overview is useful if you want a broader picture of how Universal Links fit into iOS routing.
Domain association is the foundation everything else builds on. Get that right, and the rest of Universal Links setup is straightforward.
Get deep linking tips in your inbox
One email per week. No spam.