TN3155: Debugging universal links
Investigate why your universal links are opening in a web browser instead of your app.
Overview
Universal links use applinks, an associated domains service, to link directly to content within your app without routing through a web browser or your website. When a user clicks on a universal link, the following occurs:
The universal link opens directly within the app when the app is installed on the user’s device.
Otherwise, the the link will open in the user’s default web browser.
If unfamiliar with universal links and how to support them in your code, see Supporting Associated Domains and Allowing apps and websites to link to your content. Please note that web browsers, and apps registered as browsers, are not allowed to register universal links by using the applinks service.
This document outlines how to:
Test universal links behavior
To test your universal links behavior, paste a link into your Notes app and long-press it (iOS) or control-click it (macOS) to see your options for following the link. If universal links have been configured correctly, the option to open in your app and in the web browser will both show up. The option you choose will set the default behavior for your device when following universal links from this domain in the future. To change this default choice, repeat the same steps and choose a different option.
On iOS, you can additionally test your universal links with the associated domains diagnostic tests in Developer settings through these steps:
Turn on Developer Mode in Settings. Read Enabling Developer Mode on a device for more help.
In Settings > Developer, scroll to the section labeled Universal Links and turn on Associated Domains Development.
Open Diagnostics and type in your full URL. You will receive feedback on whether this link is valid for an installed app.
If your universal links are invalid, your applinks may be configured incorrectly.
Understand applinks configuration and rules
Using the Signing and Capabilities tab in Xcode, verify the app has the Associated Domains capability which contains applinks in the form of:
applinks:<fully qualified domain>A common problem is caused by using a domain in your applinks that doesn’t have an AASA file hosted in the correct location.
Make sure your domains and AASA file paths match. To match a root domain and all subdomains with a wildcard, an AASA file should be hosted at:
https://example.com/.well-known/apple-app-site-associationThis will only work with the root domain of applinks:example.com and subdomains that are matched with the wildcard applinks:*.example.com. This will not work with specific subdomains like applinks:www.example.com or applinks:foo.example.com.
To match a specific subdomain, an AASA file should be hosted at:
https://www.example.com/.well-known/apple-app-site-associationThis will only work with applinks:www.example.com. Each specific subdomain in your applinks should have its own matching AASA file path.
Use universal links on your site
To use a universal link to open your app while already browsing in the web browser, use a different subdomain. Reasons for this could be completing a questionnaire or choosing to sign in. If a universal link has the same domain as the previous navigation, the web browser will expect the user wants to continue navigating within the browser. Review Allowing apps and websites to link your content for more information.
For an example of when to use subdomains, consider an app with the root domain applinks:example.com and an AASA containing:
"components" : [
{
"/" : "/login/*",
"comment" : "Matches any URL with a path that starts with /login/."
}]If a user is browsing your site in a web browser and chooses a login button with the link https://example.com/login, the link will not be opened in the app. The web browser will continue navigation even though the path matches the component above since the domain did not change.
To avoid this:
Add a subdomain such as
applinks:foo.example.comto your associated domains in the Signing and Capabilities tab in Xcode.Host an AASA at:
https://foo.example.com/.well-known/apple-app-site-associationGive the login button a link of:
https://foo.example.com/loginUsing a different subdomain prevents the web browser from treating the link as navigation, so it will open the link in your app.
Host and verify your AASA
If your universal links are still not opening in your app, inspect your HTTP response headers and AASA content. For example, log the HTTP response headers and AASA JSON contents in Terminal using the following command:
% curl -v https://{domain}/.well-known/apple-app-site-associationIf the response contains a 301 or 302 HTTP status code, your is doing an HTTP redirect, which is not supported when hosting the AASA file as redirects are not supported.
Instead of hosting your AASA at your root domain and redirecting to serve a subdomain, host your AASA at each domain and subdomain included in your applinks. For example, if you have a subdomain like applinks:www.example.com and the wildcard applinks:*.example.com, host one AASA file here for the subdomain:
https://www.example.com/.well-known/apple-app-site-associationand one here for the wildcard:
https://example.com/.well-known/apple-app-site-associationIf you see a 403 or 404 HTTP error, your site denied access. Generally, this happens when the AASA file path is not publicly accessible from all IP addresses or your site is blocked for some other reason. Confirm your website’s configuration allows direct access to the AASA file in the .well-known directory, in all geographical locations, from any IP address. Specific IP addresses and ranges are not published as they cannot be guaranteed. Additionally, your server should accept all user agent requests. You can verify this by setting a random user agent and confirm if your server responds with the AASA file or an error message. For example:
curl -A "MyAgent-Bot/*" https://example.com/.well-known/apple-app-site-associationVerify the format of your AASA file. It should contain either an Array of appIDs and an Array of components, as shown in Supporting Associated Domains, or fields appID of type String and paths of type Array (matching the legacy format). Please avoid mixing formats. Doing so may result in unexpected behavior for universal links. An example of the recommended format is below:
"appIDs": [ "ABCDE12345.com.example.app", "ABCDE12345.com.example.app2" ],
"components": [ {
"/": "/test/*",
"comment": "Matches any URL beginning with /test/"
},
{
"/": "/path/1/*",
"exclude": true,
"comment": "Matches any URL beginning with /path/1/ and instructs the system not to open it as a universal link"
}]While this is the older format:
"appID": "ABCDE12345.com.example.app",
"paths": [ "/test/*", NOT "/path/1/*"]To verify that your AASA is in a valid format and pattern matches correctly, use the built-in swcutil tool on a Mac. Running sudo swcutil in Terminal shows the available commands, including swcutil dl and swcutil verify.
You can use these commands in Terminal by:
Running
sudo swcutil dl -d <domain>to check that the AASA JSON can be downloaded successfully.Running
sudo swcutil verify -d <domain> -j <path-to-JSON> [-u <URL>]to check the contents of a downloaded.jsonAASA file. Verify that your domains match with-dand your URL path pattern matches with the JSON with-u. If both are successful, you will get a confirmation message.
For an app containing applinks:example.com and the AASA shown above, here is an example of using swcutil verify where “s” is the service, “a” is the App ID, and “d” is the domain:
% sudo swcutil verify -d example.com -j ./example.json -u https://example.com/test
{ s = applinks, a = ABCD123.com.example.app, d = example.com }:
Pattern "https://example.com/test" matched.Since the pattern /test is included in the AASA, the JSON has successfully pattern matched and is verified. For the path /path/1, since it is set to be excluded in the AASA, you would see a message confirming the exclusion:
Pattern "https://example.com/path/1" blocked match.If you see this message for a URL path that is not excluded, your AASA has not been matched. Take a look at the Debug through sysdiagnose section for more information.
Debug through sysdiagnose
If the output from swcutil in the section Host and verify your AASA is showing invalid universal links, take a sysdiagnose on a device with the app installed by following the linked instructions for each platform in Profiles and Logs. After you have opened the sysdiagnose, open the swcutil_show.txt file. Search for your App ID to see the information for your applinks as shown in the following example.
Service: applinks
App ID: 1234abcd.com.example
Domain: example.com
User Approval: unspecified
Site/Fmwk Approval: approved
Last Checked: 2023-08-24 10:09:00 +0000
Next Check: 2023-08-18 21:00:19 +0000User Approval: Shows the user’s decision to open links in your app or in the web browser. Review Test universal links behavior for more information.
Site/Fmwk Approval: Specifies if the Apple-managed content delivery network (CDN) has approved your universal links and pattern matched successfully. If it is approved, your universal links are working correctly.
If it is unspecified or denied, please ensure you are allowing full access to all IP addresses and the domain is included in your associated domains. For more information, review Understand Apple’s CDN.
Last Check/Next Check: Specifies the last time Apple’s CDN requested your site for your AASA and when it will check again.
Understand Apple’s CDN
When your app is installed on a device, Apple’s CDN requests your AASA file. For the CDN to successfully cache the file, it must be hosted on a domain that is available to all IP addresses and ranges, does not redirect, and is not blocked by access policies.
When testing, if you’re running your app from Xcode, using a server that is unreachable from the public internet, or testing changes faster than Apple’s CDN can cache them, use an alternate developer mode as described in Associated Domains Entitlement. This allows you to bypass Apple’s CDN and pulls your AASA directly from your domain.
Revision History
2025-05-15 Added User-Agent policies for the hosted AASA file.
2025-03-12 Clarified how web browsers interact with universal links. Made other minor editorial changes.
2024-08-13 Made minor changes, added note for port numbers.
2023-09-05 First published.
See Also
Latest
TN3205: Low-latency communication with RDMA over ThunderboltTN3206: Updating Apple Pay certificatesTN3179: Understanding local network privacyTN3190: USB audio device design considerationsTN3194: Handling account deletions and revoking tokens for Sign in with AppleTN3193: Managing the on-device foundation model’s context windowTN3115: Bluetooth State Restoration app relaunch rulesTN3192: Migrating your iPad app from the deprecated UIRequiresFullScreen keyTN3151: Choosing the right networking APITN3111: iOS Wi-Fi API overviewTN3191: IMAP extensions supported by Mail for iOS, iPadOS, and visionOSTN3134: Network Extension provider deploymentTN3189: Managing Mail background traffic loadTN3187: Migrating to the UIKit scene-based life cycleTN3188: Troubleshooting In-App Purchases availability in the App Store