Working with Amazon CloudFront and S3 is a popular and powerful combo when it comes to hosting static files, configuring custom URLs, or enabling features like Apple Universal Links (via apple-app-site-association). But it's also easy to make configuration mistakes that can cause frustrating errors like "Access Denied" or "NoSuchKey".

In this post, I’ll walk you through one of the most common mistakes, one that I recently ran into myself and how to fix it quickly.


❌ The Problem: “Access Denied” or 404 from CloudFront

access denied

Let’s say you want to serve a file like:

https://your-cloudfront-url/.well-known/apple-app-site-association

You upload the file with name apple-app-site-association, to your S3 bucket and configure a CloudFront distribution. In the process, you set:

  • Origin Domain: your-bucket.s3.region.amazonaws.com
  • Origin Path: /.well-known/apple-app-site-association

Everything seems perfect, but when you try to access the file through CloudFront, you get:

AccessDenied

or

The specified key does not exist.

🔍 What Went Wrong

Here’s the key misunderstanding:

The Origin Path in CloudFront is not the full file path, it’s a prefix that's automatically added to every request path.

So, if your CloudFront request is for:

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

And your origin path is also set to:

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

Then CloudFront tries to fetch:

S3: /.well-known/apple-app-site-association/.well-known/apple-app-site-association

Which obviously doesn’t exist — hence the error.


✅ The Correct Setup

Here’s how to fix and simplify things:

Remove the Origin Path from your CloudFront origin settings.

✅ In your S3 bucket:

  • Create a folder called .well-known
  • Upload your file inside that folder:

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

s3 folder structure

✅ Make sure CloudFront has access (via Origin Access Control).

origin access control

✅ Leave Origin path - optional as empty

Secure Bucket Policy for CloudFront OAC:
Add this to your bucket policy (replace ACCOUNT_ID and DISTRIBUTION_ID):

{
     "Version": "2012-10-17",
     "Statement": [
       {
         "Sid": "AllowCloudFrontAccessViaOAC",
         "Effect": "Allow",
         "Principal": {
           "Service": "cloudfront.amazonaws.com"
         },
         "Action": "s3:GetObject",
         "Resource": "arn:aws:s3:::your-bucket-name/*",
         "Condition": {
           "StringEquals": {
             "AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
           }
         }
       }
     ]
   }

✅ Make sure your bucket has public access disabled

s3 public access disabled

✅ Now, test the file at:

https://your-cloudfront-url/.well-known/apple-app-site-association

🎉 It should work!


🧠 Takeaway

When working with CloudFront and S3:

  • Keep the Origin Path empty unless you really need it.
  • Structure your S3 folders to match your desired URL path.
  • CloudFront appends the request path to the origin path, so watch out for unnecessary nesting.

🔄 Bonus Tip: Content-Type for apple-app-site-association

If you’re hosting apple-app-site-association, don’t forget to set the correct Content-Type in S3:

application/json

Even though the file doesn’t have a .json extension, Apple expects it to have the correct Content-Type header.


Hope this helps you avoid the same mistake and get your setup running smoothly.

If this saved you hours of debugging, you’re not alone 😄

Let me know in the comments if you’ve run into other CloudFront/S3 gotchas!