DNS, Certificates & CDN
Route 53, AWS Certificate Manager, and CloudFront: the trio that turns a deployed app into a fast, trusted, custom-domain website
Introduction
So far this course has focused on building and running things: compute, storage, databases, queues, pipelines. But an application is not finished the moment it runs in AWS. Users need a memorable domain name instead of a random CloudFront URL, a browser padlock instead of a security warning, and fast load times no matter where in the world they are. That is the job of three services that almost always show up together: Route 53 for DNS, AWS Certificate Manager (ACM) for HTTPS, and CloudFront for global content delivery.
This lesson walks through each service, then ties them together using the exact pattern that ships ByteCode's own static front ends: an S3 bucket behind a CloudFront distribution, fronted by a custom domain in Route 53 and secured with an ACM certificate. By the end, you will recognize this "last mile" architecture on sight, and know which extra services (WAF, Shield, Lambda@Edge) to reach for as requirements grow.
1. Amazon Route 53: DNS as a Managed Service
Route 53 is AWS's DNS service: it translates human-friendly domain names like bytecode-solutions.com into the IP addresses (or AWS resources) that actually serve traffic. Two concepts come up constantly:
- Hosted zones - a container for DNS records for a domain. Public hosted zones answer queries from the internet; private hosted zones answer queries only from within one or more VPCs.
- Record types -
A/AAAAmap a name to an IPv4/IPv6 address,CNAMEmaps a name to another name, andMX/TXThandle mail routing and verification/SPF records.
Alias Records vs. CNAME
An alias record is a Route 53-specific extension of a standard record (commonly an A record) that points directly at an AWS resource: a CloudFront distribution, an Application Load Balancer, an API Gateway custom domain, or an S3 website endpoint. Compared to a plain CNAME, alias records:
- Cost nothing per query and resolve instantly, since Route 53 looks up the target internally instead of doing an extra DNS hop
- Can be used at the zone apex (e.g.
bytecode-solutions.comitself), where the DNS spec forbidsCNAMErecords - Automatically track the target's IP changes, so you never hardcode an address that AWS might rotate
Beyond simple name-to-resource mapping, Route 53 supports routing policies that decide which answer to return when more than one target exists:
Simple & Weighted
Simple routing returns one fixed answer. Weighted routing splits traffic across multiple targets by percentage, ideal for canary releases and A/B tests.
Latency & Geolocation
Latency-based routing sends users to the region with the lowest round-trip time; geolocation routing answers based on the user's country or continent, useful for content licensing or compliance.
Failover Routing & Health Checks
Route 53 health checks periodically probe an endpoint (HTTP, HTTPS, or TCP). Pair them with failover routing to automatically shift traffic from a primary to a standby resource the moment the primary stops responding, no manual DNS edits required.
2. AWS Certificate Manager (ACM): Free, Renewing TLS
Every production site needs HTTPS, and HTTPS needs a TLS certificate signed by a trusted authority. ACM removes the operational pain of certificates entirely:
- Public certificates are free when attached to integrated AWS services such as CloudFront, Application Load Balancers, and API Gateway custom domains.
- Validation is DNS-based in almost every modern setup: ACM asks you to create a
CNAMErecord proving you control the domain, which CDK can create automatically when the hosted zone lives in Route 53. - Renewal is automatic for certificates that stay in use, eliminating the expired-certificate outages that plagued manually managed TLS.
- Wildcard and multi-domain (SAN) certificates let one certificate cover an entire family of subdomains, e.g.
*.bytecode-solutions.com, so new subdomains never need a new certificate request.
The Rule Everyone Trips Over: us-east-1
CloudFront is a global service, but it can only use ACM certificates that were requested or imported in the us-east-1 (N. Virginia) region, regardless of where your distribution's origin or the rest of your stack lives. Request the certificate in the wrong region and CloudFront simply will not see it as an option. Application Load Balancers always need the certificate in their own region, and API Gateway depends on the domain type: edge-optimized custom domains run on CloudFront under the hood and need a us-east-1 certificate too, while regional custom domains need one in the API's own region. The same logic catches people setting up Cognito User Pool custom domains: the Hosted UI also runs on CloudFront under the hood, so its certificate must be requested in us-east-1 as well. Always check which service will actually consume the certificate before requesting it.
3. Amazon CloudFront: Content Delivery at the Edge
CloudFront is AWS's content delivery network (CDN): a global mesh of edge locations that cache copies of your content close to your users. The first request for an asset is a cache miss and travels back to the origin; every subsequent request from nearby users is a cache hit served straight from the edge, often in single-digit milliseconds.
Origins
An origin can be an S3 bucket (REST API endpoint or, for static sites, the website endpoint via S3StaticWebsiteOrigin, which natively serves index.html for directory paths), an Application Load Balancer, API Gateway, or any custom HTTP(S) endpoint.
Viewer Protocol & Cache Behaviors
REDIRECT_TO_HTTPS upgrades plain HTTP visitors automatically. Cache behaviors let different URL paths use different origins, TTLs, and compression settings, while price classes control which edge regions you pay to use.
Custom Error Responses for SPAs
The S3 origin can answer with a 403 or a 404 for any path that does not map to a file in the bucket, the exact code depends on the origin type and bucket configuration, including React Router routes like /dashboard. Mapping both codes to 200 +/index.html hands control back to the client-side router instead of showing an error page.
OAC/OAI vs. Public Origins, and Invalidation
Origin Access Control (the modern replacement for OAI) locks an S3 bucket so only CloudFront can read it, ideal for private content. Public static sites can skip that complexity. Either way, cached files persist until their TTL expires or you run an invalidation (e.g. on every deploy via CDK's BucketDeployment) to force the edge to fetch fresh copies.
Origin Shield: An Extra Caching Layer in Front of Your Origin
Origin Shield is an optional additional caching layer that sits between CloudFront's regional edge caches and your origin. Without it, each of CloudFront's regional edge caches can independently send a cache-miss request straight to the origin, so a single globally popular object may trigger tens of simultaneous origin fetches at once. With Origin Shield enabled, all regional edge caches funnel their misses through a single designated Origin Shield region before anything reaches the origin, collapsing those concurrent fetches into at most one.
How to enable it: Origin Shield is configured per origin in the CloudFront distribution settings. You choose the AWS region closest to your origin server (not your users). In CDK, pass origin_shield_region when constructing the origin object, for example origin_shield_region="us-east-1" when your S3 bucket or ALB lives in us-east-1.
When to use it:
- Expensive or fragile origins - EC2 instances, containers, or on-premises servers where each extra request has a real compute or licensing cost. Origin Shield dramatically reduces the number of requests that escape the CloudFront layer.
- High cache-hit ratio workloads - popular media, large file downloads, or streaming video, where many edge locations would otherwise compete to re-fetch the same object during a simultaneous traffic burst.
- Multi-origin or multi-CDN architectures - if CloudFront is one of several CDNs in front of the same origin, a single Origin Shield region reduces the total fan-out hitting the origin from all paths.
- Geographically distant origins - placing Origin Shield in the AWS region nearest to the origin shortens the origin-fetch path and absorbs latency spikes for edge locations that are far away.
Cost note: Origin Shield adds a per-request charge only for requests that pass through it (i.e., cache misses that reach the Origin Shield layer). Requests served from a regional edge cache or an edge location without touching Origin Shield are not billed for it. For high-traffic sites the reduction in origin data-transfer and compute costs typically outweighs the Origin Shield fee.
4. Reference Architecture: Static Site on S3 + CloudFront + Route 53
These three services combine into one of the most common patterns in AWS: serving a single-page application from a custom domain with HTTPS and global caching. This is the exact shape ByteCode uses to deploy its own React front ends: an S3 bucket holds the built assets, CloudFront caches and serves them over HTTPS using an ACM certificate, and a Route 53 alias record points the custom domain at the distribution.
Static Site Reference Architecture
Figure 1: A Route 53 alias record points the custom domain at CloudFront, which presents an ACM certificate (requested in us-east-1, regardless of where the rest of the stack lives) and serves cached content from an S3 static-website origin. The Cognito node (bottom left) is a reminder that its Hosted UI custom domain leans on this exact same trio: a Route 53 alias, a us-east-1 ACM certificate, and a CloudFront distribution under the hood.
Zooming into a single request shows how these pieces cooperate at runtime, from DNS resolution through the TLS handshake to the eventual cache hit or miss:
Request Lifecycle
Figure 2: The first request for a path is a cache miss that travels to the S3 origin; CloudFront then serves every subsequent nearby request straight from the edge. The final exchange shows that 'Log in' is just another HTTPS request, this time to Cognito's Hosted UI, itself fronted by the same Route 53 + ACM + CloudFront trio.
In CDK, the four pieces snap together in roughly this order: bucket, certificate, distribution, then DNS record. This is a trimmed version of the pattern used in ByteCode's own front-end stacks:
DOMAIN = "app.example.com"
ROOT_DOMAIN = "example.com"
# 1. S3 bucket configured for static website hosting
bucket = s3.Bucket(self, "WebsiteBucket",
bucket_name="my-app-website",
website_index_document="index.html",
website_error_document="index.html",
block_public_access=s3.BlockPublicAccess(
block_public_policy=False,
block_public_acls=False,
ignore_public_acls=True,
restrict_public_buckets=False,
),
)
bucket.grant_public_access()
# 2. ACM certificate - MUST live in us-east-1 for CloudFront to use it,
# no matter which region the rest of the stack deploys to
certificate = acm.Certificate.from_certificate_arn(
self, "SiteCertificate",
certificate_arn=f"arn:aws:acm:us-east-1:{account_id}:certificate/{cert_id}",
)
# 3. CloudFront distribution: HTTPS only, custom domain, SPA error routing
distribution = cloudfront.Distribution(self, "SiteDistribution",
default_root_object="index.html",
default_behavior=cloudfront.BehaviorOptions(
origin=origins.S3StaticWebsiteOrigin(bucket),
viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
),
domain_names=[DOMAIN],
certificate=certificate,
error_responses=[
cloudfront.ErrorResponse(http_status=403, response_http_status=200,
response_page_path="/index.html", ttl=Duration.minutes(60)),
cloudfront.ErrorResponse(http_status=404, response_http_status=200,
response_page_path="/index.html", ttl=Duration.minutes(60)),
],
)
# 4. Route 53 alias record -> CloudFront (no CNAME, no extra DNS lookup cost)
hosted_zone = route53.HostedZone.from_lookup(self, "Zone", domain_name=ROOT_DOMAIN)
route53.ARecord(self, "SiteAliasRecord",
zone=hosted_zone,
record_name="app",
target=route53.RecordTarget.from_alias(targets.CloudFrontTarget(distribution)),
)Notice the S3StaticWebsiteOrigin (not the generic S3Origin): it targets the bucket's website endpoint, which is what makes directory paths resolve to index.html automatically. The error_responses list is what lets React Router own client-side navigation, and the alias record at the bottom is what makes the custom domain "just work" with zero extra DNS hops.
Once a site is live behind CloudFront and Route 53, a handful of additional services commonly join the stack to add security, run logic at the edge, or handle traffic patterns CloudFront is not built for.
5. AWS WAF: Filtering Malicious Traffic at the Edge
AWS WAF is a web application firewall that inspects HTTP(S) requests against a Web ACL attached to CloudFront, an Application Load Balancer, API Gateway, or AppSync. Rules can be AWS Managed Rule Groups (covering SQL injection, known bad inputs, and bot control) or custom rules such as rate-based limits, geographic match conditions, and IP allow/deny lists.
When to Use
Public-facing sites and APIs that need to block common web exploits or throttle abusive clients before they ever reach the origin, especially once real traffic, and real attackers, start arriving.
Cost Considerations
Billed per Web ACL per month, per rule added to it, and per million requests inspected; AWS Managed Rule Groups carry their own subscription fees on top. Costs track how many rules you run and how much traffic you receive, not how much data you serve.
6. AWS Shield: Always-On DDoS Protection
AWS Shield provides DDoS protection for internet-facing resources. Shield Standard runs automatically, at no charge, on every CloudFront distribution and Route 53 hosted zone, absorbing common network and transport-layer (layer 3/4) floods. Shield Advanced adds layer-7 detection, near-real-time attack visibility, and access to AWS's DDoS Response Team.
When to Use
Standard needs no setup, it is already protecting you. Reach for Advanced only on business-critical, internet-facing workloads where an extended DDoS event, and the scaling charges it can trigger, would be genuinely costly.
Cost Considerations
Shield Standard is free and always on. Shield Advanced carries a substantial fixed monthly commitment, billed at the organization level under a typical one-year term, plus usage-based data transfer fees, so it is generally reserved for large-scale or high-profile applications rather than small projects.
7. Amazon Cognito: Managed Identity and Authentication
Amazon Cognito covers two distinct jobs that are often confused because the console presents them side by side: User Pools handle authentication (who is this person?), and Identity Pools handle authorization to AWS services (what AWS resources can this person reach directly?). Each can be used independently, but they are frequently combined.
User Pools: Authentication and JWT Issuance
A User Pool is a managed user directory. It handles sign-up, sign-in, MFA, password policies, and token issuance. After a successful sign-in, Cognito returns three tokens:
- ID token - a JWT containing claims about the authenticated user (sub, email, custom attributes). This is what the app reads to know who is logged in, and what the
HttpJwtAuthorizeron API Gateway validates. - Access token - a JWT that authorizes calls to Cognito's own APIs (e.g. updating user attributes) and is checked by API Gateway when scope-based authorization is configured.
- Refresh token - a long-lived token (30 days by default) the app exchanges silently for a fresh ID and access token, keeping the session alive without re-prompting the user.
User Pools also support social and enterprise identity federation: users can sign in with Google, Apple, Facebook, or any SAML 2.0 or OIDC-compliant provider. Cognito acts as an OpenID Connect broker, normalizing the external identity into the same User Pool JWT your application already trusts. Pairing a User Pool with a Cognito (JWT) authorizer on API Gateway is the HttpJwtAuthorizer pattern this course builds in the API Gateway lesson, the Capstone's api_stack.py, and this very portal's own backend.
Hosted UI and Custom Domains
The Hosted UI is a pre-built, AWS-managed login page that handles OAuth 2.0 / OIDC flows for you. Giving it a custom domain (e.g. auth.bytecode-solutions.com) is itself an instance of this lesson's trio: Cognito provisions a CloudFront distribution under the hood, so its custom domain needs a Route 53 alias record and a us-east-1 ACM certificate just like any other CloudFront distribution. If the defaults are not enough, you can build a fully custom login screen and call Cognito's authentication APIs directly, avoiding any redirect to an AWS-hosted domain.
Identity Pools: Temporary AWS Credentials
An Identity Pool solves a different problem: it exchanges an identity token for short-lived AWS credentials so a client can call AWS services directly, without routing every request through a backend proxy. The pool accepts tokens from several sources:
- Cognito User Pool JWTs - the most common pairing
- Social provider tokens (Google, Apple, Facebook, Amazon) used without a User Pool
- Developer-authenticated identities - where your own backend asserts the user's identity to the pool
- Unauthenticated (guest) access - the pool assigns a restricted IAM role even to anonymous visitors
Under the hood, the Identity Pool calls STS AssumeRoleWithWebIdentity, passing the validated token and the ARN of a pre-configured IAM role. STS returns temporary credentials (an access key, secret key, and session token) that default to 1 hour and can be extended up to 12 hours via the IAM role's MaxSessionDuration setting. The client uses them to sign AWS API requests with SigV4. Separate IAM roles for authenticated and unauthenticated users control what each group can actually do.
Web Identity Federation
Web Identity Federation is the broader pattern that Identity Pools implement: a client presents a token from a trusted external identity provider (IdP), and AWS exchanges it for temporary IAM credentials via STS AssumeRoleWithWebIdentity. The trust relationship works as follows:
- An IAM role carries a trust policy permitting STS to assume it on behalf of a specific identity provider, with a condition matching the expected audience (your app's client ID).
- The client obtains a token from the IdP (Cognito User Pool, Google, Apple, etc.), sends it to STS via the Identity Pool, and receives back credentials scoped to that role's permission policy.
- The resulting credentials are short-lived and never stored as long-lived access keys on end-user devices - the key security advantage over distributing IAM credentials directly.
Web Identity Federation vs. an API Proxy
If every AWS call the client needs to make can be proxied through your API (Lambda + API Gateway), prefer that: credentials stay server-side and you get a single place to add validation, logging, and business logic. Web Identity Federation is the right choice when:
- The client uploads or downloads files directly to/from S3, avoiding double data transfer through a Lambda
- You are building a mobile or IoT app that needs to call AWS services (DynamoDB, IoT Core, S3) with minimal latency
- Routing all traffic through a backend proxy would be impractical at your data volume or transfer size
The sequence below shows the complete token exchange from a User Pool sign-in through to a direct S3 upload, the most common real-world application of Web Identity Federation:
Web Identity Federation: User Pool + Identity Pool + S3
Figure 3: Web Identity Federation in practice. The app signs in to the User Pool, exchanges the resulting JWT with the Identity Pool, which calls STS AssumeRoleWithWebIdentity under the hood, and uses the returned short-lived credentials to call S3 directly. No long-lived AWS access keys are ever stored on the client.
The component view below shows how User Pools, Identity Pools, STS, and external identity providers fit together. A Cognito User Pool and a social IdP are both valid token sources for the same Identity Pool:
Cognito User Pool + Identity Pool + Web Identity Federation
Figure 4: Cognito User Pool and Identity Pool as separate layers. The User Pool (or an external IdP) proves identity. The Identity Pool calls STS AssumeRoleWithWebIdentity to exchange that proof for temporary AWS credentials. The IAM role trust policy controls which identity providers and audiences STS will honor.
When to Use
Any app that needs to authenticate end users and protect an API without maintaining a custom identity system. Add an Identity Pool when clients need direct access to AWS services, or when you need to grant unauthenticated guests a restricted set of permissions (e.g. read-only access to a public S3 prefix).
Cost Considerations
User Pools include a free tier of monthly active users (MAUs), after which pricing scales per MAU and varies by feature tier. Standard flows and the Hosted UI sit in the lower tiers; adaptive authentication pushes usage into pricier ones. Identity Pool pricing is separate and MAU-based: you pay per unique identity that obtains credentials in a given month, with a free tier included.
8. CloudFront Functions & Lambda@Edge: Running Your Own Code at the Edge
CloudFront Functions and Lambda@Edge let you run your own code at CloudFront's edge locations instead of, or before, reaching the origin. CloudFront Functions execute lightweight JavaScript: header rewrites, redirects, and simple authorization checks, in sub-millisecond time. Lambda@Edge runs full Lambda functions, with more languages, more memory, and longer execution windows, at regional edge caches for heavier transformations.
When to Use
CloudFront Functions for high-volume, simple request/response tweaks where speed and cost matter most; Lambda@Edge when the logic needs to call other AWS services, run longer, or ship a larger deployment package.
Cost Considerations
CloudFront Functions are billed per invocation only, with no compute-duration charge, making them extremely cheap at scale. Lambda@Edge bills both per request and per GB-second of compute, at a premium over standard Lambda pricing, so picking the lightest tool for the job has a direct effect on the bill.
9. AWS Global Accelerator: Routing Non-Cacheable Traffic Globally
AWS Global Accelerator improves availability and performance for workloads that are not cacheable HTTP, by routing traffic over the AWS global network through two static anycast IP addresses, with automatic regional failover.
When to Use
Gaming, VoIP, IoT, and other latency-sensitive TCP/UDP workloads, or any case where clients need fixed entry-point IPs and instant failover across regions, the kind of traffic CloudFront's HTTP-centric caching model was never built to serve.
Cost Considerations
A fixed hourly fee per accelerator, plus a per-GB data transfer premium on top of standard data transfer charges. That premium makes it noticeably pricier than CloudFront for cacheable HTTP content, which is exactly why CloudFront stays the default choice for static sites and APIs.
10. AWS Private CA: Issuing Internally Trusted Certificates
AWS Private CA is a managed private certificate authority for issuing certificates that should only ever be trusted inside your organization, the things ACM's free public certificates are not meant for: service-to-service mTLS, internal dashboards, and IoT device identities.
When to Use
Internal or service-to-service TLS where a publicly trusted certificate would be unnecessary, or undesirable, and you need control over the certificate hierarchy, validity periods, and revocation.
Cost Considerations
A monthly fee per certificate authority, with a lower-cost tier for short-lived certificates, plus per-certificate issuance charges that taper off with volume. It is meaningfully pricier than ACM's free public certificates, so it earns its place only when there is a genuine internal-PKI need.
11. Putting It All Together: The Extended Reference Architecture
None of the six services above replace the Route 53 + ACM + CloudFront trio, they extend it: WAF and Shield add security at the perimeter, CloudFront Functions and Lambda@Edge run custom logic at the edge, Global Accelerator covers the non-cacheable traffic CloudFront was never built to serve, and Cognito and Private CA round out the identity and certificate needs that grow alongside a live site. The diagram below takes Figure 1's reference architecture and layers each of these services into the same scene, in the role it would actually play:
Extended Architecture: Security, Edge Compute & Beyond
Figure 5: The same Route 53 + ACM + CloudFront + S3 core from Figure 1, now wrapped in the services from this section. WAF and Shield inspect and protect traffic at the perimeter, CloudFront Functions / Lambda@Edge run custom logic at the edge, AWS Private CA issues internal-only certificates alongside ACM's public ones, Cognito's Hosted UI leans on the same trio for its own custom domain, and Global Accelerator stands beside CloudFront as the alternative for traffic it was never built to cache.
Key Takeaways
- Route 53 alias records - free to query, resolve instantly, and are the only way to point a zone apex at an AWS resource like CloudFront or an ALB.
- ACM certificates for CloudFront must live in us-east-1 - regardless of where your distribution's origin or the rest of your infrastructure runs; the same applies to API Gateway edge-optimized domains and Cognito Hosted UI custom domains (both run on CloudFront under the hood), while ALBs and regional API Gateway domains need the certificate in their own region.
- S3 + CloudFront + custom error responses is the standard SPA pattern - using the S3 website endpoint origin and mapping 403/404 to 200 +
/index.htmlhands routing control to React Router. - Cached content needs explicit invalidation - either by waiting out the TTL or triggering an invalidation on deploy, which CDK's
BucketDeploymentcan automate. - WAF, Shield, and Lambda@Edge extend the same edge layer - for application-layer firewalling, DDoS protection, and running custom logic close to your users.
- Cognito has two distinct layers - User Pools handle authentication and JWT issuance; Identity Pools implement Web Identity Federation, exchanging those JWTs (or tokens from Google, Apple, or other external IdPs) for short-lived AWS credentials via STS
AssumeRoleWithWebIdentity. Use Identity Pools when clients need direct access to S3, DynamoDB, or other AWS services without a backend proxy in between.