Skip to content
Tolinku
Tolinku
Sign In Start Free
Deep Linking · · 5 min read

CDN and AASA Caching: Avoiding Universal Link Failures

By Tolinku Staff
|
Tolinku industry trends dashboard screenshot for deep linking blog posts

CDNs introduce caching layers between your server and Apple's AASA validation. When configured incorrectly, CDNs can serve stale, incorrect, or blocked AASA files, breaking Universal Links silently. This article covers how Apple fetches and caches AASA files, how popular CDNs interact with this process, and how to configure each one correctly.

For AASA file setup, see AASA file setup. For debugging AASA issues, see debugging AASA file issues. For iOS troubleshooting, see the iOS troubleshooting guide.

How Apple Fetches AASA Files

The Two-Layer Cache

When a user installs or updates your app, iOS fetches the AASA file through two layers:

  1. Apple's CDN (app-site-association.cdn-apple.com): Apple caches AASA files on its own CDN. iOS devices fetch from Apple's CDN, not directly from your server.
  2. Your CDN: Apple's servers fetch the AASA file from your domain, which may be served through your CDN (Cloudflare, Fastly, AWS CloudFront, etc.).
iOS device → Apple CDN → Your CDN → Your origin server

Both layers can cache stale data:

Layer Cache Duration You Can Control?
Apple's CDN 24-48 hours (typically) No
Your CDN Depends on your configuration Yes

When Apple Fetches

Apple fetches the AASA file:

  • When the app is installed.
  • When the app is updated.
  • Periodically in the background (timing varies by iOS version).

Apple does not fetch on every link tap. The AASA file is cached on the device.

CDN-Specific Configuration

Cloudflare

Cloudflare is the most common CDN to cause AASA issues. Problems occur because:

  1. Bot protection: Cloudflare's bot detection can block Apple's AASA fetcher.
  2. Caching: Cloudflare caches the file, but cache purges may not propagate immediately.
  3. Page Rules: Rules that redirect or block requests can interfere.

Configuration:

Create a Page Rule or Configuration Rule for the AASA path:

URL: yourdomain.com/.well-known/apple-app-site-association

Settings:
- Cache Level: Bypass (or set short TTL)
- Security Level: Essentially Off
- Bot Fight Mode: Off for this path
- Browser Integrity Check: Off

Alternatively, add a Transform Rule to ensure the correct Content-Type:

When: URI Path equals "/.well-known/apple-app-site-association"
Then: Set response header Content-Type to "application/json"

Cloudflare Workers (advanced):

addEventListener('fetch', event => {
  const url = new URL(event.request.url);
  if (url.pathname === '/.well-known/apple-app-site-association') {
    event.respondWith(handleAASA(event.request));
  }
});

async function handleAASA(request) {
  const response = await fetch(request);
  const newResponse = new Response(response.body, response);
  newResponse.headers.set('Content-Type', 'application/json');
  newResponse.headers.set('Cache-Control', 'public, max-age=3600');
  return newResponse;
}

AWS CloudFront

CloudFront configuration for AASA files:

  1. Origin: Point to your origin server (S3, EC2, etc.).
  2. Behavior: Create a cache behavior for /.well-known/apple-app-site-association.
  3. TTL: Set a short TTL (1 hour) so changes propagate quickly.
  4. Headers: Forward the Accept header to the origin.

CloudFront cache policy:

Setting Value
Minimum TTL 0
Maximum TTL 3600 (1 hour)
Default TTL 3600
Header forwarding None (or Accept)

Fastly

Fastly VCL configuration:

sub vcl_recv {
  if (req.url == "/.well-known/apple-app-site-association") {
    set req.http.X-Pass = "true";
  }
}

sub vcl_fetch {
  if (req.http.X-Pass == "true") {
    set beresp.ttl = 1h;
    set beresp.http.Content-Type = "application/json";
  }
}

Vercel

Vercel serves files from the public/ directory. Place the AASA file at:

public/.well-known/apple-app-site-association

Add a vercel.json header configuration:

{
  "headers": [
    {
      "source": "/.well-known/apple-app-site-association",
      "headers": [
        { "key": "Content-Type", "value": "application/json" },
        { "key": "Cache-Control", "value": "public, max-age=3600" }
      ]
    }
  ]
}

Nginx (as a caching reverse proxy)

If you use Nginx as a caching layer:

location = /.well-known/apple-app-site-association {
    proxy_pass http://upstream;
    proxy_cache_valid 200 1h;
    add_header Content-Type application/json;
    add_header Cache-Control "public, max-age=3600";
}

Cache Invalidation Strategies

When you update your AASA file, you need to invalidate caches at multiple levels:

Your CDN Cache

CDN Invalidation Method
Cloudflare Purge cache for the specific URL in the dashboard or API
AWS CloudFront Create an invalidation for /.well-known/apple-app-site-association
Fastly Purge by URL via dashboard or API
Vercel Redeploy (cache is purged automatically)

Apple's CDN Cache

You cannot directly invalidate Apple's cache. Options:

  1. Wait: Apple refreshes its cache within 24-48 hours.
  2. Device-level refresh: Delete and reinstall the app on test devices.
  3. Developer mode: Use applinks:yourdomain.com?mode=developer in your entitlement during development. This bypasses Apple's CDN and fetches directly from your server.

Verification

After updating and purging:

# Check your CDN
curl -I https://yourdomain.com/.well-known/apple-app-site-association

# Check Apple's cache
curl https://app-site-association.cdn-apple.com/a/v1/yourdomain.com

If your CDN returns the new version but Apple's CDN does not, Apple has not refreshed yet.

Failure CDN Cause Fix
Apple cannot fetch AASA Bot protection blocks Apple's fetcher Whitelist Apple's user agents or disable bot protection for the AASA path
AASA returns HTML instead of JSON CDN serves a custom error page Ensure the origin returns JSON and the CDN forwards it
AASA returns 301/302 CDN redirects (e.g., HTTP to HTTPS, www to non-www) Apple does not follow redirects; serve AASA directly
Stale AASA after update CDN cache not purged Purge CDN cache and wait for Apple's CDN refresh
AASA returns with wrong Content-Type CDN overrides Content-Type header Set Content-Type: application/json in CDN configuration
Intermittent failures CDN edge nodes serving different versions Purge all edge locations, not just one region

Testing CDN Configuration

Verify from Multiple Locations

Use a tool like curl with different DNS resolvers, or use a service like KeyCDN Tools to test from multiple geographic locations.

Monitor AASA Availability

Set up monitoring to alert you if the AASA file becomes inaccessible:

# Simple monitoring script
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
  https://yourdomain.com/.well-known/apple-app-site-association)

if [ "$HTTP_STATUS" != "200" ]; then
  echo "ALERT: AASA file returned HTTP $HTTP_STATUS"
fi

Run this check every 5-15 minutes. An inaccessible AASA file will not break Universal Links immediately (iOS caches the file on-device), but it will prevent new installs from configuring Universal Links.

Tolinku for AASA Hosting

Tolinku hosts your AASA file on infrastructure optimized for Apple's validation process. The platform handles Content-Type headers, avoids redirects, and serves the file from a reliable CDN with proper cache headers. This eliminates CDN-related AASA failures entirely. See the Universal Links developer guide for configuration.

For domain association, see universal links domain association. For the complete Universal Links guide, see universal links: everything you need to know.

Get deep linking tips in your inbox

One email per week. No spam.

Ready to add deep linking to your app?

Set up Universal Links, App Links, deferred deep linking, and analytics in minutes. Free to start.