How to generate and host an apple-app-site-association file (the right way)

A complete guide to creating, hosting, and serving an AASA file for Universal Links, App Clips, Passkeys, and Handoff — including the gotchas that silently break iOS deep linking.

If you’re shipping an iOS app that needs Universal Links, App Clips, Passkeys, or Handoff, you need an apple-app-site-association file — a small JSON document hosted on your domain that tells iOS which app to open for which URLs.

It sounds simple. The file itself is maybe 20 lines of JSON. But Apple is uncompromising about how it must be served, and a single misconfigured header silently breaks deep linking with no error message anywhere — links just open Safari instead of your app.

This guide walks through the right way to generate the file, the half-dozen ways hosting can go wrong, and how to verify everything works before submitting your app.

What an AASA file actually does

When a user taps a link to your domain on an iOS device, Apple’s swcd daemon (the “shared web credentials” daemon) checks an apple-app-site-association file hosted at your domain. If the file says the URL pattern matches one your app handles, iOS opens your app instead of Safari.

The same file declares four separate capabilities:

Capability Key in the AASA file What it enables
Universal Links applinks Tap a web link → open in your app
App Clips appclips Invoke a lightweight version of your app from NFC, QR, or links
Passkeys / Password AutoFill webcredentials Share saved credentials between your app and website
Handoff activitycontinuation Continue an activity across devices

All four ride on the same file at the same path — you only need to host one.

Where the file goes

Apple looks for the file at:

https://your-domain.com/.well-known/apple-app-site-association

Some legacy Apple documentation and most third-party validators (Branch’s checker, getuniversal.link) also check the legacy root path:

https://your-domain.com/apple-app-site-association

Both should return the same file with the same headers. Modern iOS uses /.well-known/, but serving both removes a class of false-positive failures in debugging tools.

What the file must contain

A minimal file for Universal Links looks like this:

{
  "applinks": {
    "details": [
      {
        "appIDs": ["ABCDE12345.com.yourapp"],
        "components": [
          { "/": "/products/*", "comment": "Product pages" },
          { "/": "/admin/*", "exclude": true }
        ]
      }
    ]
  }
}

A few notes from the spec:

  • appIDs (plural, with a capital “I” in IDs) is the modern field. Older guides may show appID (singular) inside an apps array — that’s the legacy format and doesn’t take advantage of multiple bundle IDs.
  • The Team ID is the 10-character alphanumeric prefix from your Apple Developer account.
  • Path patterns support * (any characters), ? (any single character), and NOT for exclusions. They are case-sensitive.
  • Query strings and fragment identifiers are ignored when matching paths.
  • An empty apps array under applinks is not required in the modern format, despite what some older documentation suggests.

If you also want App Clips, Passkeys, and Handoff:

{
  "applinks": { "details": [ /* ... */ ] },
  "appclips": { "apps": ["ABCDE12345.com.yourapp.Clip"] },
  "webcredentials": { "apps": ["ABCDE12345.com.yourapp"] },
  "activitycontinuation": { "apps": ["ABCDE12345.com.yourapp"] }
}

The serving requirements that silently break things

This is where most setups fail. Apple’s swcd is strict:

Requirement Why it bites
HTTPS only An HTTP-only domain is invisible to Apple.
TLS 1.2 or higher Older TLS configs fail without an error.
No redirects (no 301/302) Apple won’t follow them. The file must be at the canonical URL with a 200 response.
Content-Type: application/json The wrong MIME type causes a silent parse failure.
No .json extension on the filename Filename is literally apple-app-site-association — no extension.
Public, no auth If the file is gated behind a login or IP allowlist, Apple’s CDN can’t fetch it.
128 KB max file size Apple’s swcd rejects larger files. With sensible app/path counts you’ll never hit this, but keep an eye on it if you generate the file dynamically.
AASA-Bot/1.0.0 user agent Cloudflare’s bot challenge can block it. If you front your origin with Cloudflare, allowlist the bot.

The iOS 14+ CDN behavior

Starting in iOS 14, devices don’t fetch your AASA file directly. Apple’s CDN does it on behalf of every device:

https://app-site-association.cdn-apple.com/a/v1/your-domain.com

The CDN refreshes roughly every 24–48 hours, so a freshly-deployed change can take a while to propagate. To inspect what the CDN currently has for your domain, hit that URL directly in a browser.

If you need to bypass the CDN during development — e.g., you’re iterating on path patterns and want changes to land instantly — change your Xcode Associated Domains entitlement from:

applinks:your-domain.com

to:

applinks:your-domain.com?mode=developer

That tells iOS to fetch the AASA file directly from your origin, no CDN involved. Remove the ?mode=developer flag before submitting to the App Store — it’s a development-only mode.

iOS does not re-scrape the AASA file on app updates from the App Store (a known limitation). To force a refresh during development, delete and reinstall the app. Long-press a link in the Notes app to verify whether iOS picked up your latest config.

Subdomains are separate domains

Apple treats myapp.com and www.myapp.com as distinct domains for AASA purposes. Each needs its own AASA file at its own domain root, and each needs its own line in your Xcode Associated Domains capability:

applinks:myapp.com
applinks:www.myapp.com

If you only want one canonical version, redirect the other at your DNS provider — most registrars support apex-to-www (or the reverse) redirects natively.

Three ways to actually host the file

1. Static hosting (GitHub Pages, S3, Firebase Hosting)

Cheap and fine for a single domain. The catch: you have to manage Content-Type headers manually (GitHub Pages can’t set them per-file; you’ll need a custom workflow), and you can’t easily share the same domain with marketing content.

2. Edit your nginx/Caddy/Apache config

If you already run a web server on your domain, add a location block that serves the file with the correct headers and no redirects. Easy if you’re a sysadmin; surprisingly fiddly if you’re not.

3. Use OrbitKit

This is what we built. Configure your App IDs and paths in a dashboard, point your DNS, and OrbitKit serves the file at both /.well-known/apple-app-site-association and /apple-app-site-association with the right Content-Type, no redirects, automatic HTTPS, no Cloudflare bot-challenge problems, and a 128 KB size guard. Free tier covers a single app.

The right answer depends on whether you already have web infrastructure for the domain. If you do and you’re comfortable editing config files, options 1 or 2 are fine. If you just want it to work on the first try, that’s our pitch.

How to verify it actually works

  1. HTTP check with curl:
    curl -I https://your-domain.com/.well-known/apple-app-site-association
    

    You want HTTP/2 200, content-type: application/json, and no redirects.

  2. JSON validity:
    curl -s https://your-domain.com/.well-known/apple-app-site-association | jq .
    

    If jq errors, your JSON is broken.

  3. Apple’s CDN view: Visit https://app-site-association.cdn-apple.com/a/v1/your-domain.com and confirm Apple has the same content.

  4. Branch’s free validator: branch.io/resources/aasa-validator — checks both paths, validates JSON, flags the common gotchas.

  5. On-device test: Send yourself a link via Notes or iMessage. Long-press it. You should see “Open in <App Name>” as the first option.

Common failure modes and what to check

Symptom Likely cause
Link opens Safari instead of app AASA missing, wrong Content-Type, behind a redirect, or app entitlement mismatch
apple-app-site-association returns 404 File at wrong path, or behind app routing that doesn’t serve static files
Works in dev, breaks in prod Different domain or subdomain (each needs its own file), or Cloudflare bot-mode blocking AASA-Bot
Works for a while, then stops App was updated via App Store but iOS didn’t re-fetch — delete and reinstall
Path matches local but not online Apple’s CDN cached an older version; hit the CDN URL to compare
Apple Developer Forums shows successful AASA fetch but app still doesn’t open App entitlement missing or wrong Bundle ID — re-check the applinks: line in Associated Domains

Further reading