By default a distribution answers on d1234abcd.cloudfront.net with a certificate AWS provides. Putting it on your own domain over HTTPS takes three things: a certificate in the right region, the domain registered on the distribution, and DNS pointing at it.
The one rule to remember before anything else: the ACM certificate must be in us-east-1.
CloudFront is a global service whose control plane runs in us-east-1, and it can only attach certificates from that region. This holds regardless of where your bucket, load balancer or anything else lives.
aws acm request-certificate --region us-east-1 \
--domain-name example.com \
--subject-alternative-names 'www.example.com' '*.example.com' \
--validation-method DNS \
--query CertificateArn --output textWhat makes this trip so many people is the failure mode: a certificate in the wrong region is perfectly valid, validates successfully, and simply does not appear in CloudFront's list of choices. Nothing reports an error. If you already have one elsewhere for an ALB, leave it and request a second in us-east-1. ACM certificates are free, and there is no downside to holding both.
Request the wildcard alongside the apex. *.example.com covers www and any future subdomain but not example.com itself, which is why both are listed above.
aws acm describe-certificate --region us-east-1 \
--certificate-arn "$CERT_ARN" \
--query 'Certificate.DomainValidationOptions[].[DomainName,ResourceRecord.Name,ResourceRecord.Value]' \
--output table-------------------------------------------------------------------------
| example.com | _a1b2c3.example.com. | _d4e5f6.acm-validations.aws. |
-------------------------------------------------------------------------
Create that CNAME. In Route 53 one command does it:
aws acm describe-certificate --region us-east-1 --certificate-arn "$CERT_ARN" \
--query 'Certificate.DomainValidationOptions[0].ResourceRecord'
# then create the CNAME with change-resource-record-setsaws acm wait certificate-validated --region us-east-1 --certificate-arn "$CERT_ARN"Prefer DNS validation over email. It completes in minutes without anyone reading a mailbox, and, the part that matters long term, ACM renews automatically for as long as the validation record stays in place. Deleting that CNAME after validation is a quiet way to break renewal a year later. Leave it.
A certificate stuck in PENDING_VALIDATION is nearly always a missing trailing dot, a record created in the wrong hosted zone, or a subdomain zone that is delegated elsewhere.
Two things must both be set: the alternate domain name (CNAME) and the certificate.
"Aliases": { "Quantity": 2, "Items": ["example.com", "www.example.com"] },
"ViewerCertificate": {
"ACMCertificateArn": "arn:aws:acm:us-east-1:123456789012:certificate/â¦",
"SSLSupportMethod": "sni-only",
"MinimumProtocolVersion": "TLSv1.2_2021",
"CertificateSource": "acm"
}CloudFront verifies that the certificate covers every alias, and rejects the update otherwise. Every name in Aliases must be in the certificate's subject or SANs.
sni-only is what you want. The alternative, vip, assigns dedicated IP addresses at a substantial monthly cost per distribution, and exists only for clients too old to support SNI. A population that no longer meaningfully exists.
For an apex domain, a CNAME is not allowed by the DNS specification. Route 53's alias record solves this:
aws route53 change-resource-record-sets --hosted-zone-id "$ZONE_ID" --change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "example.com",
"Type": "A",
"AliasTarget": {
"HostedZoneId": "Z2FDTNDATAQYW2",
"DNSName": "d1234abcd.cloudfront.net",
"EvaluateTargetHealth": false
}
}
}]
}'Z2FDTNDATAQYW2 is the fixed hosted zone ID for CloudFront. The same for every distribution in every account. Alias records also cost nothing to query, unlike CNAMEs.
Add an AAAA alias with the same target so IPv6 clients resolve too. Forgetting it produces intermittent failures for exactly the users who have IPv6, which is a confusing report to receive.
Outside Route 53, use a CNAME for subdomains and whatever apex-flattening your provider offers (ALIAS, ANAME, or CNAME flattening) for the apex.
"MinimumProtocolVersion": "TLSv1.2_2021"The minimum protocol version sets which TLS versions and ciphers CloudFront accepts from viewers. TLSv1.2_2021 is a sensible modern floor; TLS 1.0 and 1.1 policies still exist and fail most compliance scans. Raising it drops genuinely ancient clients, which is the trade you are making deliberately.
Note it only applies when you use a custom domain with sni-only. On the default *.cloudfront.net name, AWS controls the policy.
The viewer-to-CloudFront leg and the CloudFront-to-origin leg are configured separately, and leaving the second as plain HTTP is a common oversight:
"CustomOriginConfig": {
"HTTPPort": 80,
"HTTPSPort": 443,
"OriginProtocolPolicy": "https-only",
"OriginSslProtocols": {"Quantity": 1, "Items": ["TLSv1.2"]}
}https-only is the right choice for any custom origin. match-viewer sounds appealing and means an HTTP viewer request travels to your origin unencrypted, so avoid it.
The origin's certificate must be publicly trusted and match the origin's domain name. CloudFront will not accept a self-signed certificate, and there is no option to skip verification. A 502 immediately after switching to https-only is usually this.
S3 origins do not need any of it: CloudFront always uses HTTPS to S3.
InvalidViewerCertificate: The specified SSL certificate doesn't exist,
isn't in us-east-1 region, isn't valid, or doesn't include a valid
certificate chain.
Almost always the region. Re-request in us-east-1.
CNAMEAlreadyExists: One or more of the CNAMEs you provided are already
associated with a different resource
That alias is on another distribution, possibly in a different account. CloudFront enforces global uniqueness of alternate domain names. Remove it from the old distribution first.
InvalidViewerCertificate: ... the certificate does not cover the
alternate domain names
An alias is missing from the certificate's SANs. Note *.example.com does not cover example.com.
ERR_SSL_PROTOCOL_ERROR in the browser
Usually DNS pointing at the distribution before the alias and certificate were attached, so CloudFront answers with its default certificate, which does not match the hostname requested.
| Task | Command |
|---|---|
| Request a cert | aws acm request-certificate --region us-east-1 --validation-method DNS |
| Get validation records | aws acm describe-certificate --query 'Certificate.DomainValidationOptions' |
| Wait for validation | aws acm wait certificate-validated --region us-east-1 |
| List certs in us-east-1 | aws acm list-certificates --region us-east-1 |
| Route 53 alias | AliasTarget.HostedZoneId = Z2FDTNDATAQYW2 |
| Check what is served | curl -sI https://example.com | head -5 |
| Inspect the certificate | openssl s_client -connect example.com:443 -servername example.com |
*.example.com does not cover example.com. Request both.sni-only, not vip. Dedicated IPs cost a lot and solve a problem that no longer exists.Z2FDTNDATAQYW2, plus an AAAA record.MinimumProtocolVersion to TLSv1.2_2021 or better.https-only, never match-viewer.<MCQExercise exercise={{ id: 'aws-cf-ssl-quiz1', title: 'A browser SSL error on a new domain', description: 'DNS for shop.example.com was pointed at a CloudFront distribution, but browsers report ERR_SSL_PROTOCOL_ERROR. The distribution works fine on its cloudfront.net name. What was missed?', options: [ { id: 'a', text: 'shop.example.com was never added to the distribution's Aliases with a matching us-east-1 certificate, so CloudFront answers with its default certificate' }, { id: 'b', text: 'CloudFront requires a CNAME rather than an alias record for subdomains' }, { id: 'c', text: 'The distribution needs to be disabled and re-enabled for DNS changes to apply' }, { id: 'd', text: 'A new distribution must be created for each custom domain' }, ], correctId: 'a', explanation: 'Pointing DNS at a distribution is only one of the three required steps. CloudFront also has to know the hostname, added to Aliases, and hold a certificate from us-east-1 that covers it. Without both, an incoming request for shop.example.com is served the default *.cloudfront.net certificate, which does not match the name the browser asked for, and TLS fails before any HTTP response. Add the alias and the certificate together, since CloudFront validates that the certificate covers every alias.', wrongExplanations: { b: 'Both CNAME and Route 53 alias records work for a subdomain; alias is required only at the apex.', c: 'DNS changes take effect on their own as records propagate. No distribution restart exists or is needed.', d: 'One distribution supports many alternate domain names, provided the certificate covers them all.', }, }} />
<MCQExercise exercise={{ id: 'aws-cf-ssl-quiz2', title: 'A certificate that expires anyway', description: 'An ACM certificate validated by DNS a year ago has failed to renew, and the team tidied up "unused" DNS records a few months back. What happened?', options: [ { id: 'a', text: 'ACM re-validates through the same CNAME at renewal, so deleting the validation record after issuance breaks automatic renewal' }, { id: 'b', text: 'DNS-validated certificates must be manually renewed every twelve months' }, { id: 'c', text: 'ACM certificates attached to CloudFront never renew automatically' }, { id: 'd', text: 'The certificate needed to be re-requested in us-east-1 before renewal' }, ], correctId: 'a', explanation: 'ACM handles renewal without intervention, but only while it can still prove control of the domain, and for DNS validation that proof is the _<hash>.example.com CNAME pointing at acm-validations.aws. The record looks like leftover clutter once the certificate has issued, which is exactly why it gets deleted during cleanups, and the failure surfaces up to a year later when renewal quietly fails. Recreate the validation record, leave it in place permanently, and treat those CNAMEs as infrastructure rather than temporary artefacts.', wrongExplanations: { b: 'ACM renews automatically; manual renewal is not part of the normal lifecycle.', c: 'Certificates in use by CloudFront renew automatically like any other, provided validation still succeeds.', d: 'The certificate was already in the correct region. It had been working for a year.', }, }} />
The DNS specification forbids a CNAME at the zone apex, so use a Route 53 alias record of type A pointing at the distribution's domain name, with hosted zone ID Z2FDTNDATAQYW2. The same value for every CloudFront distribution. Add an AAAA alias with the same target so IPv6 clients resolve too. Outside Route 53, use whatever apex flattening your DNS provider offers, commonly called ALIAS, ANAME or CNAME flattening.
Alternate domain names are globally unique across all of CloudFront, so the hostname is already attached to another distribution. Frequently an old one in the same account, sometimes one in a different account entirely. Remove the alias from the existing distribution first, wait for that change to deploy, then add it to the new one. If the other distribution is not yours, AWS Support can help verify domain ownership and release it.
No. *.example.com matches www.example.com and any other single-level subdomain, but not example.com itself, and not a.b.example.com. Request both names on one certificate: --domain-name example.com --subject-alternative-names '*.example.com'. CloudFront validates that the certificate covers every entry in Aliases, so a missing apex is rejected at configuration time rather than causing a mysterious runtime error.
sni-only, essentially always. Server Name Indication lets many distributions share addresses by sending the requested hostname during the TLS handshake, and every browser and client in current use supports it. The dedicated IP option (vip) costs a significant monthly fee per distribution and exists only for clients predating SNI, which in practice means Windows XP and similar. There is no performance or security advantage to it.
Yes for a custom origin. Set OriginProtocolPolicy to https-only. Viewer-side and origin-side TLS are configured separately, so a distribution serving HTTPS to browsers can still be fetching over plain HTTP behind the scenes. Avoid match-viewer, which forwards an HTTP viewer request to the origin unencrypted. The origin's certificate must be publicly trusted and match its hostname, since CloudFront offers no way to skip verification; a 502 right after enabling https-only is usually that. S3 origins always use HTTPS and need no configuration.
Get DNS working on Route 53: public and private hosted zones, the delegation step people forget, alias records, health checks and how TTL sets failover speed.
Create the Route 53 record types you will use: A, AAAA, CNAME, MX, TXT, CAA and SRV. Covers UPSERT semantics, SPF and DMARC records and common errors.
Choose a Route 53 routing policy: weighted, latency, failover, geolocation, geoproximity or multivalue. Learn the Set Identifier rule and why canaries drift.