deep-linking

Universal Links Not Opening? Every Cause and How to Fix Each One

Universal Links opening Safari instead of your app, or opening the app but doing nothing? Triage both failures fast: testing mistakes, AASA and entitlement issues, and the cold-start handler bug.

WarpLink Team··12 min read

TL;DR: "Universal Links not opening" is really two different bugs. Either the link opens Safari instead of your app (an association problem: the AASA file, the entitlement, or how you are testing), or the link opens your app but lands on a blank screen (a handler problem: your app never reads the incoming URL). Before changing any config, rule out the testing mistakes in Step 0, because most "broken" Universal Links are working fine and being tested wrong. Then use the two sections below to fix whichever symptom you actually have.

Two Bugs Wearing the Same Costume

A developer on the Apple Developer Forums described the version of this that wastes the most time: "despite having a correctly configured AASA file and associated domains setup, our application does not consistently handle Universal Links and we simply end up getting a blank page... after successful login the control never gets passed back to the iOS mobile app."

Notice there are actually two complaints in there. "Opens Safari instead of the app" and "opens the app but shows a blank page" feel like the same problem to a user, but they have nothing to do with each other. The first is iOS deciding not to hand the URL to your app. The second is your app getting the URL and doing nothing with it. Fixing one does nothing for the other, which is why this drags on: people keep re-checking their AASA file when the real bug is in their scene handler, or rewriting their routing when the real bug is a redirect on the AASA URL.

So the first move is to figure out which bug you have. Does the app open at all when you tap the link?

But first, the false alarms.

Step 0: Confirm It's Actually Broken

More than half of "Universal Links not opening" reports are testing mistakes, not bugs. iOS deliberately does not trigger Universal Links in several situations, and every one of them looks identical to a broken link. Rule these out before you touch a single config file.

  • You pasted the URL into Safari's address bar. Typing or pasting a link into the address bar is direct navigation, not a link tap. Universal Links only fire when the user taps an actual link. Test by tapping a real link in Notes or Messages, never the address bar.
  • You tapped a link to the same domain you were already on. If you are browsing yourdomain.com in Safari and tap a link to another page on yourdomain.com, iOS treats it as in-page navigation and never opens the app. Universal Links only trigger from a different domain. This is why deep linking services use a separate short-link domain.
  • You tapped inside an in-app browser. Links opened inside Instagram, X, Facebook, or any app using an embedded web view often bypass Universal Links entirely. This is the embedding app's behavior, not yours. Test in real Safari or Messages.
  • You are on the Simulator. Universal Links are unreliable on the iOS Simulator. Always test Universal Links on a physical device.
  • You previously chose "open in Safari" for this domain. After your app opens a link, iOS shows the site name as a breadcrumb in the top corner. If you (or a tester) once tapped it to view the page in Safari, iOS remembers that and stops auto-opening the app for the whole domain. To re-enable, long-press a link and choose to open it in the app.
  • You edited the AASA file but did not reinstall. iOS only picks up your AASA file at install, on app update, and roughly once a week after that. Editing the file does nothing for a device that already has the app. Delete and reinstall to force a refetch.

If you have ruled all of these out and the link still misbehaves, you have a real bug. Pick the matching symptom below.

Opens Safari Instead of the App

The link opens Safari or the App Store, and your app never launches. iOS could not verify that your app is allowed to handle this URL. There are three places that verification breaks.

1. The AASA file (most common)

This is the single most frequent real cause. iOS verifies your app against the apple-app-site-association file on your domain, and the fetch is strict: it must return a 200 with Content-Type: application/json, with no redirects, and the appIDs value inside has to be your Team ID joined to your bundle ID. A redirect, a wrong content type, or a missing Team ID prefix breaks the whole thing silently.

A quick first check:

If that is anything other than a clean 200 with application/json and no redirect, you found it. The file has enough failure modes (CDN caching, the appIDs prefix gotcha, components paths, developer mode) that it gets its own guide: see Apple App Site Association Not Working: The iOS Universal Links Debug Checklist and work that checklist top to bottom. Come back here once the file checks out.

2. The Associated Domains entitlement

If the file is correct, the problem is often the app side of the association.

  • Your app must declare applinks:yourdomain.com in its Associated Domains capability. Use the bare domain: no https://, no path, no port.
  • applinks:yourdomain.com does not cover www.yourdomain.com or any other subdomain. List each host separately, or use the applinks:*.yourdomain.com wildcard (iOS 14 and later). The wildcard matches one subdomain level only, and you still need a valid AASA reachable at the specific subdomain you link to.
  • Adding the capability in Xcode is not enough on its own. The provisioning profile that signs the build must include the Associated Domains capability. If you enabled it after generating the profile, regenerate the profile and re-sign.

3. The device has not refetched the file

Covered in Step 0, but it is worth repeating because it masquerades as every other cause: if you changed anything in the file or the entitlement, a device that already has the app will not see the change until it refetches. Delete and reinstall, or use developer mode to bypass Apple's cache during testing. Append ?mode=developer to the entitlement (applinks:yourdomain.com?mode=developer) and enable Settings > Developer > Associated Domains Development to fetch directly from your server in real time. Remove it before you ship.

One thing that is usually NOT the cause: iCloud Private Relay. Private Relay masks the device IP, which affects IP-based deferred deep link attribution, a separate system. It does not break the AASA fetch or Universal Link routing. If your link opens Safari, Private Relay is almost never why. Look at the file and the entitlement first.

Opens the App but Does Nothing

This is the blank-page bug, and it is a completely different problem. iOS verified the association, launched your app, and handed it the URL. Your app just never read it. The fix is always in your app's code, never in the AASA file.

The cause is almost always that the incoming-link handler is missing for your app's actual lifecycle. iOS delivers a Universal Link as an NSUserActivity, and where it arrives depends on whether you use SwiftUI, a SceneDelegate, or a plain AppDelegate, and on whether the app was already running.

Warm start: the handler that fires when the app is already alive

If your app uses a SceneDelegate, the link arrives in scene(_:continue:). A common mistake is implementing application(_:continue:restorationHandler:) on the AppDelegate instead, which does not fire when scenes are active, so the URL vanishes and you get a blank screen.

On macOS or Mac Catalyst, use .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) instead. On iOS, .onOpenURL already covers Universal Links.

When the app is not running, tapping the link launches it, and the activity arrives through a different door. With a SceneDelegate, you have to read it in scene(_:willConnectTo:options:) from the connection options, because scene(_:continue:) does not fire on a cold launch.

If you only handle the warm-start path, links work when the app is in the background and silently fail when it is closed. That single asymmetry is behind a large share of "it works sometimes" reports.

The login round-trip case

The forum quote above ends with "after successful login the control never gets passed back." This is the OAuth or web-checkout round-trip: your app opens a web page for sign-in, the provider redirects back to a https://yourdomain.com/callback Universal Link, and instead of returning to the app it stays in the web view. Two things to check. First, the callback path has to be claimed in your AASA components and handled by the same scene handlers above. Second, the deeper reason Universal Links are unreliable as OAuth redirect targets: they only fire on a user tap, never on a server redirect. The 302 that sends the user back from the identity provider cannot trigger Universal Link routing at all. The reliable pattern is ASWebAuthenticationSession with a custom callback scheme (callbackURLScheme), which intercepts the return itself rather than relying on Universal Links. An HTTPS callback is possible but only on iOS 17.4 and later.

A Note on iOS Versions and Cross-Platform

  • iOS version differences are real. Behavior around deferred handling and callback timing has shifted across iOS 15 through 18, and a setup that works on one version can fail on another. Test on the actual OS versions your users run, not just your newest device.
  • React Native, Expo, Capacitor, and Flutter all sit on top of the same native machinery. A Universal Link that fails in a cross-platform app fails for one of the native reasons above. Debug it as an iOS problem first: check the AASA file and the entitlement, then confirm the framework's link listener is wired up.

The Short Version

Two of the three "opens Safari" causes are about hosting the AASA file correctly, and the blank-page bug is about wiring up handlers correctly. WarpLink shortens both.

When you register an app, WarpLink generates and serves the AASA file for you, at the edge, with the right content type and no redirects, so the file-hosting failure modes do not happen on a WarpLink domain. And the SDK collapses the handler work into one call: you pass the incoming URL to WarpLink.handleDeepLink(url) from your scene handler or .onOpenURL, and it returns the destination, custom parameters, and attribution data, on the main thread, for both cold and warm start. You still own the routing, but not the boilerplate that the blank-page bug hides in. The full setup is in the iOS deep links docs.

This guide stays vendor-neutral on the debugging itself. Whether you host the file yourself or not, the triage is the same.

Frequently Asked Questions

Why are my Universal Links not opening in iOS 17 or 18? First rule out the Step 0 testing mistakes, which catch most reports. If it is a real failure, the cause is the same on iOS 17 and 18 as on earlier versions: the AASA file, the entitlement, or a missing link handler. Behavior around callback timing has shifted across versions, so test on the specific OS your users run.

How do I re-enable a Universal Link that iOS disabled? If you once chose to view a domain's links in Safari, iOS stops auto-opening the app for that domain. Long-press a link to that domain and choose to open it in the app. There is no API to detect or reset this.

Why does Private Relay break my Universal Links? It almost certainly does not. Private Relay masks the device IP, which affects IP-based deferred deep link attribution, not Universal Link routing or the AASA fetch. If your links open Safari, check the file and the entitlement, not Private Relay.

Universal Links work in the background but not on a fresh launch. That is the cold-start handler gap. You are handling the warm-start path (scene(_:continue:) or .onOpenURL) but not reading connectionOptions.userActivities in scene(_:willConnectTo:options:). Add the cold-start handler shown above.

The link opens my app but lands on the home screen. The app received the URL but is not routing it, or is not reading it at all. Confirm your link handler is implemented for your app's lifecycle and that it parses the path. This is a code problem, not an AASA problem.

Once your links open the right screen reliably, the next question is attribution: which link and channel actually drove the tap and the install. That is what WarpLink handles after the link resolves.

Create a free WarpLink account and let the AASA file and the cold-start handling come set up out of the box.

WarpLink Team

Building the open deep linking platform for developers and small teams.

Related Posts