How to Set Up a Custom Domain for Amazon CloudFront

Blog / Amazon Web Services · October 14, 2021 · Updated June 10, 2026 · 9 min read
How to Set Up a Custom Domain for Amazon CloudFront

By default, every Amazon CloudFront distribution answers on a random domain like d111111abcdef8.cloudfront.net. That works, but it isn't what you want customers, your application, or search engines to see. This guide walks through pointing a branded custom domain — say cdn.example.com, or even the apex example.com — at a CloudFront distribution end to end, using a free TLS certificate from AWS Certificate Manager (ACM) and a Route 53 alias record (or a CNAME at any DNS provider).

Across 12+ years and 50+ delivered projects we've put CloudFront in front of S3 buckets, single-page apps, APIs, and Django media. The steps below are the current, 2026-accurate path — the old aws iam upload-server-certificate workflow is obsolete; use ACM instead. If you'd rather hand it off, our AWS consulting team can set it up and harden it for you.

The four moving parts

Connecting a custom domain to CloudFront always comes down to the same four pieces. Get these right and everything clicks into place:

  1. Alternate domain name (CNAME) — you tell the distribution which custom hostname(s) it should answer to.
  2. A TLS certificate that covers that hostname — issued (or imported) in ACM, and it must live in the us-east-1 Region.
  3. DNS that points the hostname at the distribution — a Route 53 alias A/AAAA record, or a CNAME at a third-party provider.
  4. Viewer settings — force HTTPS, set a sane minimum TLS version, and serve over SNI.

Prerequisites

  • A CloudFront distribution that already serves your origin (S3, an ALB, an API, etc.).
  • Control of the domain's DNS zone — ideally a Route 53 public hosted zone.
  • The AWS CLI v2 or the AWS Console, with permission to use ACM, CloudFront, and Route 53.

Step 1 — Request a TLS certificate in ACM (us-east-1)

This is the one fact everyone trips over: the ACM certificate used by a CloudFront distribution must be in the us-east-1 (N. Virginia) Region — no matter where your origin, your bucket, or your team lives. CloudFront is a global service whose control plane reads certificates only from us-east-1. A perfectly valid certificate sitting in eu-west-2 or ap-southeast-2 simply won't appear in the distribution's certificate dropdown.

ACM public certificates are free and renew automatically, so prefer a requested ACM certificate over an imported one whenever you can validate by DNS. Request the certificate for the exact hostname you'll use, and add any extras (for example both example.com and www.example.com):

# CRITICAL: --region us-east-1 for CloudFront, even if your origin is elsewhere.
aws acm request-certificate \
  --domain-name cdn.example.com \
  --validation-method DNS \
  --region us-east-1

# Show the CNAME record you must create to prove you own the domain:
aws acm describe-certificate \
  --certificate-arn arn:aws:acm:us-east-1:111122223333:certificate/abcd-1234 \
  --region us-east-1 \
  --query 'Certificate.DomainValidationOptions[].ResourceRecord'

Step 2 — Validate the certificate with DNS

ACM gives you a CNAME record (a long random name plus a value) for each domain on the certificate. Add those records to your DNS zone. If your zone is in Route 53, the ACM console offers a one-click Create records in Route 53 button; otherwise paste the CNAME into your provider.

DNS validation beats email validation because, as long as the record stays in place, ACM renews the certificate automatically before it expires — no annual scramble. Validation usually completes within a few minutes of the record propagating, after which the certificate status flips to Issued.

Step 3 — Add the alternate domain name to the distribution

Now associate the hostname with CloudFront. In the distribution's Settings, edit:

  • Alternate domain names (CNAMEs) — add cdn.example.com (one per line; add example.com / www.example.com if relevant).
  • Custom SSL certificate — pick the ACM certificate you just had issued in us-east-1.

Every hostname in Alternate domain names must be covered by the certificate (exact match, or a wildcard like *.example.com), or CloudFront rejects the save. Here's the same change with boto3 — handy for infrastructure-as-code or scripted rollouts:

import boto3

cf = boto3.client("cloudfront")
dist_id = "E1234567890ABC"

# CloudFront is a global service; the viewer certificate must reference an
# ACM cert in us-east-1, no matter where the origin lives.
acm_arn = "arn:aws:acm:us-east-1:111122223333:certificate/abcd-1234"

current = cf.get_distribution_config(Id=dist_id)
etag = current["ETag"]
config = current["DistributionConfig"]

# 1) Add the custom domain to Alternate Domain Names (CNAMEs).
config["Aliases"] = {"Quantity": 1, "Items": ["cdn.example.com"]}

# 2) Attach the ACM certificate and serve over SNI (free; dedicated IP is legacy).
config["ViewerCertificate"] = {
    "ACMCertificateArn": acm_arn,
    "Certificate": acm_arn,
    "CertificateSource": "acm",
    "SSLSupportMethod": "sni-only",
    "MinimumProtocolVersion": "TLSv1.2_2021",
}

cf.update_distribution(Id=dist_id, IfMatch=etag, DistributionConfig=config)
print("Distribution updating - allow a few minutes to deploy.")

Step 4 — Point DNS at the distribution

With the certificate attached and the alternate domain name saved, the final step is DNS: send the hostname to the distribution's *.cloudfront.net domain. You have two options.

For Route 53, create an alias record (not a CNAME) of type A, plus a matching AAAA for IPv6, targeting the distribution. Alias records are special AWS records that resolve at the zone apex and carry no query charge. The AliasTarget always uses CloudFront's fixed hosted-zone ID Z2FDTNDATAQYW2:

{
  "Comment": "Alias cdn.example.com -> CloudFront distribution",
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "cdn.example.com",
        "Type": "A",
        "AliasTarget": {
          "HostedZoneId": "Z2FDTNDATAQYW2",
          "DNSName": "d111111abcdef8.cloudfront.net",
          "EvaluateTargetHealth": false
        }
      }
    }
  ]
}

At a third-party DNS provider (Cloudflare, Namecheap, GoDaddy, and the like), create a CNAME for the subdomain pointing at the distribution domain — for example cdn.example.com CNAME d111111abcdef8.cloudfront.net.

Which to choose:

Capability Route 53 alias (A/AAAA) Third-party CNAME
Works at the apex (example.com) Yes No (needs ALIAS/ANAME/flattening)
Works for a subdomain (cdn.example.com) Yes Yes
Query cost to the distribution No charge Standard provider DNS
IPv6 Add an AAAA alias too One CNAME covers both
Target AliasTarget = distribution domain CNAME = dxxxx.cloudfront.net

The decisive factor is usually the apex: a plain CNAME is illegal at a zone apex (example.com with no subdomain), so root-domain setups need a Route 53 alias or a provider that offers ALIAS/ANAME/CNAME-flattening.

Handling the apex (root) domain

If you want example.com itself — no www, no cdn — served by CloudFront, you can't use a CNAME: DNS forbids a CNAME from coexisting with the SOA/NS records at the apex. Two clean options:

  • Route 53 alias A/AAAA records at the apex pointing straight at the distribution (recommended, and free for the queries).
  • A provider with apex flattening — Cloudflare's CNAME flattening, other providers' ALIAS/ANAME records — that emulates the same behaviour.

Either way, remember to add the apex (example.com) to both the ACM certificate and the distribution's Alternate domain names.

Lock down the viewer side: HTTPS, TLS, HTTP/2 and HTTP/3

A custom domain served over plain HTTP defeats the purpose. In the distribution's default cache behavior, set:

  • Viewer protocol policy: Redirect HTTP to HTTPS, so every request is upgraded.
  • Minimum TLS version (security policy): TLSv1.2_2021 — the current recommended floor; it drops legacy protocols while staying broadly compatible.
  • SSL support method: SNI only. SNI is free and supported by every modern client; the legacy dedicated-IP option is expensive and almost never needed.
  • Supported HTTP versions: enable HTTP/2 and HTTP/3 for faster connection setup and resistance to head-of-line blocking.

If your origin is S3: use Origin Access Control (OAC)

When CloudFront sits in front of a private S3 bucket, lock the bucket so it can only be read through CloudFront. The modern mechanism is Origin Access Control (OAC) — it replaces the legacy Origin Access Identity (OAI) and, unlike OAI, supports SSE-KMS encryption and all AWS Regions. Create an OAC, attach it to the S3 origin, and let CloudFront update the bucket policy so only the distribution's signed requests are allowed.

If the bucket also serves browser fetches (fonts, XHR), pair OAC with the right CORS configuration — see our walkthrough on CORS with Amazon S3 and CloudFront. For Django projects shipping static and media files this way, serving Django static and media over S3 + CloudFront covers the app side. Scope the IAM permissions tightly while you're at it — our notes on AWS IAM roles and policies help.

Add security headers with a response headers policy

You no longer need a Lambda@Edge function or a CloudFront Function just to send security headers. Attach a response headers policy to the cache behavior to inject HSTS, X-Content-Type-Options, Referrer-Policy, a Content-Security-Policy, and CORS headers — all managed and versioned by CloudFront, with no code to maintain. Turn on Strict-Transport-Security (HSTS) once you're confident HTTPS is solid across the whole domain.

Verify it works

Give CloudFront a few minutes to redeploy (status returns to Deployed) and DNS a moment to propagate, then check from your terminal:

# 1) Confirm the hostname resolves toward CloudFront.
dig +short cdn.example.com

# 2) Check the response headers, HTTP version and redirect.
curl -sSI https://cdn.example.com/ | head

# 3) Inspect the served certificate's subject and issuer.
openssl s_client -connect cdn.example.com:443 -servername cdn.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer

Common gotchas

  • Certificate missing from the dropdown → it isn't in us-east-1, or it isn't yet Issued.
  • CNAMEAlreadyExists error → that hostname is attached to another distribution; remove it there first.
  • Browser shows the *.cloudfront.net name or a cert warning → the hostname isn't in Alternate domain names, or the certificate doesn't cover it.
  • Apex won't resolve → you used a CNAME at the root; switch to a Route 53 alias.
  • 502 / MissingKey from S3 → OAC isn't attached, or the bucket policy wasn't updated to allow the distribution.

Standing up a CDN is often one piece of a larger move to AWS; if you're re-platforming, our cloud migration services cover the rest.

Frequently Asked Questions

Why must the ACM certificate be in us-east-1?

Because CloudFront is a global service whose control plane reads certificates only from the us-east-1 (N. Virginia) Region. Your origin, bucket, and team can be anywhere, but the certificate you attach to the distribution has to be requested or imported in us-east-1, or it won't even appear in the selector.

Should I use a Route 53 alias record or a CNAME?

Use a Route 53 alias A/AAAA record when your DNS is in Route 53 — it works at the apex, costs nothing per query, and resolves straight to the distribution. Use a CNAME only for subdomains at a third-party DNS provider. For a root/apex domain you must use an alias (or a provider's ALIAS/flattening feature), because a plain CNAME is illegal at the apex.

Can I use a domain registered outside Route 53?

Yes. The registrar is irrelevant; only DNS hosting matters. Add the ACM validation CNAME and the CNAME to the distribution at whatever DNS provider hosts the zone (Cloudflare, GoDaddy, Namecheap, and so on). You only need Route 53 if you want alias records — typically for an apex domain.

How long until the certificate and domain start working?

DNS validation in ACM usually completes within a few minutes of the record propagating. A CloudFront configuration change redeploys in a few minutes, after which the status returns to Deployed. Your custom domain then resolves as soon as the new DNS record's TTL has propagated — often minutes, occasionally up to an hour.

Do I need Origin Access Control (OAC)?

Only when your origin is a private S3 bucket that you want readable solely through CloudFront. OAC is the current best practice and replaces the legacy OAI — it adds SSE-KMS support and works in all Regions. For public origins, ALBs, or APIs you don't need OAC.

How do I force all traffic to HTTPS?

Set the cache behavior's viewer protocol policy to Redirect HTTP to HTTPS, attach your ACM certificate, and set the minimum TLS version to TLSv1.2_2021. For extra hardening, add a Strict-Transport-Security (HSTS) header through a CloudFront response headers policy once HTTPS is stable.

Share this article