{"id":2032,"date":"2026-08-14T17:00:00","date_gmt":"2026-08-14T22:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=2032"},"modified":"2026-03-07T03:50:27","modified_gmt":"2026-03-07T08:50:27","slug":"cdn-and-aasa-caching","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/cdn-and-aasa-caching\/","title":{"rendered":"CDN and AASA Caching: Avoiding Universal Link Failures"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">CDNs introduce caching layers between your server and Apple&#39;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.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For AASA file setup, see <a href=\"https:\/\/tolinku.com\/blog\/aasa-file-setup\/\">AASA file setup<\/a>. For debugging AASA issues, see <a href=\"https:\/\/tolinku.com\/blog\/debugging-aasa-file\/\">debugging AASA file issues<\/a>. For iOS troubleshooting, see the <a href=\"https:\/\/tolinku.com\/docs\/troubleshooting\/ios\/\">iOS troubleshooting guide<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How Apple Fetches AASA Files<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">The Two-Layer Cache<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When a user installs or updates your app, iOS fetches the AASA file through two layers:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Apple&#39;s CDN<\/strong> (<code>app-site-association.cdn-apple.com<\/code>): Apple caches AASA files on its own CDN. iOS devices fetch from Apple&#39;s CDN, not directly from your server.<\/li>\n<li><strong>Your CDN<\/strong>: Apple&#39;s servers fetch the AASA file from your domain, which may be served through your CDN (Cloudflare, Fastly, AWS CloudFront, etc.).<\/li>\n<\/ol>\n\n\n\n<pre><code>iOS device \u2192 Apple CDN \u2192 Your CDN \u2192 Your origin server\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Both layers can cache stale data:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Layer<\/th>\n<th>Cache Duration<\/th>\n<th>You Can Control?<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>Apple&#39;s CDN<\/td>\n<td>24-48 hours (typically)<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td>Your CDN<\/td>\n<td>Depends on your configuration<\/td>\n<td>Yes<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">When Apple Fetches<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Apple fetches the AASA file:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>When the app is installed.<\/li>\n<li>When the app is updated.<\/li>\n<li>Periodically in the background (timing varies by iOS version).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Apple does <strong>not<\/strong> fetch on every link tap. The AASA file is cached on the device.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">CDN-Specific Configuration<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Cloudflare<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Cloudflare is the most common CDN to cause AASA issues. Problems occur because:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Bot protection<\/strong>: Cloudflare&#39;s bot detection can block Apple&#39;s AASA fetcher.<\/li>\n<li><strong>Caching<\/strong>: Cloudflare caches the file, but cache purges may not propagate immediately.<\/li>\n<li><strong>Page Rules<\/strong>: Rules that redirect or block requests can interfere.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Configuration<\/strong>:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Create a Page Rule or Configuration Rule for the AASA path:<\/p>\n\n\n\n<pre><code>URL: yourdomain.com\/.well-known\/apple-app-site-association\n\nSettings:\n- Cache Level: Bypass (or set short TTL)\n- Security Level: Essentially Off\n- Bot Fight Mode: Off for this path\n- Browser Integrity Check: Off\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Alternatively, add a Transform Rule to ensure the correct <code>Content-Type<\/code>:<\/p>\n\n\n\n<pre><code>When: URI Path equals &quot;\/.well-known\/apple-app-site-association&quot;\nThen: Set response header Content-Type to &quot;application\/json&quot;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Cloudflare Workers<\/strong> (advanced):<\/p>\n\n\n\n<pre><code class=\"language-javascript\">addEventListener(&#39;fetch&#39;, event =&gt; {\n  const url = new URL(event.request.url);\n  if (url.pathname === &#39;\/.well-known\/apple-app-site-association&#39;) {\n    event.respondWith(handleAASA(event.request));\n  }\n});\n\nasync function handleAASA(request) {\n  const response = await fetch(request);\n  const newResponse = new Response(response.body, response);\n  newResponse.headers.set(&#39;Content-Type&#39;, &#39;application\/json&#39;);\n  newResponse.headers.set(&#39;Cache-Control&#39;, &#39;public, max-age=3600&#39;);\n  return newResponse;\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">AWS CloudFront<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">CloudFront configuration for AASA files:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Origin<\/strong>: Point to your origin server (S3, EC2, etc.).<\/li>\n<li><strong>Behavior<\/strong>: Create a cache behavior for <code>\/.well-known\/apple-app-site-association<\/code>.<\/li>\n<li><strong>TTL<\/strong>: Set a short TTL (1 hour) so changes propagate quickly.<\/li>\n<li><strong>Headers<\/strong>: Forward the <code>Accept<\/code> header to the origin.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">CloudFront cache policy:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Setting<\/th>\n<th>Value<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>Minimum TTL<\/td>\n<td>0<\/td>\n<\/tr>\n<tr>\n<td>Maximum TTL<\/td>\n<td>3600 (1 hour)<\/td>\n<\/tr>\n<tr>\n<td>Default TTL<\/td>\n<td>3600<\/td>\n<\/tr>\n<tr>\n<td>Header forwarding<\/td>\n<td>None (or <code>Accept<\/code>)<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Fastly<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Fastly VCL configuration:<\/p>\n\n\n\n<pre><code class=\"language-vcl\">sub vcl_recv {\n  if (req.url == &quot;\/.well-known\/apple-app-site-association&quot;) {\n    set req.http.X-Pass = &quot;true&quot;;\n  }\n}\n\nsub vcl_fetch {\n  if (req.http.X-Pass == &quot;true&quot;) {\n    set beresp.ttl = 1h;\n    set beresp.http.Content-Type = &quot;application\/json&quot;;\n  }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Vercel<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Vercel serves files from the <code>public\/<\/code> directory. Place the AASA file at:<\/p>\n\n\n\n<pre><code>public\/.well-known\/apple-app-site-association\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Add a <code>vercel.json<\/code> header configuration:<\/p>\n\n\n\n<pre><code class=\"language-json\">{\n  &quot;headers&quot;: [\n    {\n      &quot;source&quot;: &quot;\/.well-known\/apple-app-site-association&quot;,\n      &quot;headers&quot;: [\n        { &quot;key&quot;: &quot;Content-Type&quot;, &quot;value&quot;: &quot;application\/json&quot; },\n        { &quot;key&quot;: &quot;Cache-Control&quot;, &quot;value&quot;: &quot;public, max-age=3600&quot; }\n      ]\n    }\n  ]\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Nginx (as a caching reverse proxy)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If you use Nginx as a caching layer:<\/p>\n\n\n\n<pre><code class=\"language-nginx\">location = \/.well-known\/apple-app-site-association {\n    proxy_pass http:\/\/upstream;\n    proxy_cache_valid 200 1h;\n    add_header Content-Type application\/json;\n    add_header Cache-Control &quot;public, max-age=3600&quot;;\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Cache Invalidation Strategies<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When you update your AASA file, you need to invalidate caches at multiple levels:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Your CDN Cache<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>CDN<\/th>\n<th>Invalidation Method<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>Cloudflare<\/td>\n<td>Purge cache for the specific URL in the dashboard or API<\/td>\n<\/tr>\n<tr>\n<td>AWS CloudFront<\/td>\n<td>Create an invalidation for <code>\/.well-known\/apple-app-site-association<\/code><\/td>\n<\/tr>\n<tr>\n<td>Fastly<\/td>\n<td>Purge by URL via dashboard or API<\/td>\n<\/tr>\n<tr>\n<td>Vercel<\/td>\n<td>Redeploy (cache is purged automatically)<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Apple&#39;s CDN Cache<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You <strong>cannot<\/strong> directly invalidate Apple&#39;s cache. Options:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Wait<\/strong>: Apple refreshes its cache within 24-48 hours.<\/li>\n<li><strong>Device-level refresh<\/strong>: Delete and reinstall the app on test devices.<\/li>\n<li><strong>Developer mode<\/strong>: Use <code>applinks:yourdomain.com?mode=developer<\/code> in your entitlement during development. This bypasses Apple&#39;s CDN and fetches directly from your server.<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Verification<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">After updating and purging:<\/p>\n\n\n\n<pre><code class=\"language-bash\"># Check your CDN\ncurl -I https:\/\/yourdomain.com\/.well-known\/apple-app-site-association\n\n# Check Apple&#39;s cache\ncurl https:\/\/app-site-association.cdn-apple.com\/a\/v1\/yourdomain.com\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If your CDN returns the new version but Apple&#39;s CDN does not, Apple has not refreshed yet.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Common CDN-Related Failures<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Failure<\/th>\n<th>CDN Cause<\/th>\n<th>Fix<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>Apple cannot fetch AASA<\/td>\n<td>Bot protection blocks Apple&#39;s fetcher<\/td>\n<td>Whitelist Apple&#39;s user agents or disable bot protection for the AASA path<\/td>\n<\/tr>\n<tr>\n<td>AASA returns HTML instead of JSON<\/td>\n<td>CDN serves a custom error page<\/td>\n<td>Ensure the origin returns JSON and the CDN forwards it<\/td>\n<\/tr>\n<tr>\n<td>AASA returns 301\/302<\/td>\n<td>CDN redirects (e.g., HTTP to HTTPS, www to non-www)<\/td>\n<td>Apple does not follow redirects; serve AASA directly<\/td>\n<\/tr>\n<tr>\n<td>Stale AASA after update<\/td>\n<td>CDN cache not purged<\/td>\n<td>Purge CDN cache and wait for Apple&#39;s CDN refresh<\/td>\n<\/tr>\n<tr>\n<td>AASA returns with wrong Content-Type<\/td>\n<td>CDN overrides Content-Type header<\/td>\n<td>Set <code>Content-Type: application\/json<\/code> in CDN configuration<\/td>\n<\/tr>\n<tr>\n<td>Intermittent failures<\/td>\n<td>CDN edge nodes serving different versions<\/td>\n<td>Purge all edge locations, not just one region<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Testing CDN Configuration<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Verify from Multiple Locations<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Use a tool like <code>curl<\/code> with different DNS resolvers, or use a service like <a href=\"https:\/\/tools.keycdn.com\/curl\" rel=\"nofollow noopener\" target=\"_blank\">KeyCDN Tools<\/a> to test from multiple geographic locations.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Monitor AASA Availability<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Set up monitoring to alert you if the AASA file becomes inaccessible:<\/p>\n\n\n\n<pre><code class=\"language-bash\"># Simple monitoring script\nHTTP_STATUS=$(curl -s -o \/dev\/null -w &quot;%{http_code}&quot; \\\n  https:\/\/yourdomain.com\/.well-known\/apple-app-site-association)\n\nif [ &quot;$HTTP_STATUS&quot; != &quot;200&quot; ]; then\n  echo &quot;ALERT: AASA file returned HTTP $HTTP_STATUS&quot;\nfi\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Tolinku for AASA Hosting<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/tolinku.com\/features\/deep-linking\">Tolinku<\/a> hosts your AASA file on infrastructure optimized for Apple&#39;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 <a href=\"https:\/\/tolinku.com\/docs\/developer\/universal-links\/\">Universal Links developer guide<\/a> for configuration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For domain association, see <a href=\"https:\/\/tolinku.com\/blog\/universal-links-domain-association\/\">universal links domain association<\/a>. For the complete Universal Links guide, see <a href=\"https:\/\/tolinku.com\/blog\/universal-links-everything-you-need-to-know\/\">universal links: everything you need to know<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Prevent Universal Link failures caused by CDN caching. Configure Cloudflare, Fastly, and other CDNs to serve AASA files correctly.<\/p>\n","protected":false},"author":2,"featured_media":2031,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"CDN and AASA Caching: Avoiding Universal Link Failures","rank_math_description":"Prevent Universal Link failures caused by CDN caching. Configure Cloudflare, Fastly, and other CDNs to serve AASA files correctly.","rank_math_focus_keyword":"CDN AASA caching","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-cdn-and-aasa-caching.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-cdn-and-aasa-caching.png","footnotes":""},"categories":[11],"tags":[76,122,640,639,641,20,24,642,87,22],"class_list":["post-2032","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-deep-linking","tag-aasa","tag-apple","tag-caching","tag-cdn","tag-cloudflare","tag-deep-linking","tag-ios","tag-server-configuration","tag-troubleshooting","tag-universal-links"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/2032","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=2032"}],"version-history":[{"count":1,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/2032\/revisions"}],"predecessor-version":[{"id":2033,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/2032\/revisions\/2033"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/2031"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=2032"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=2032"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=2032"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}