Skip to content

CloudFront

2 min read · main part only; folded sections are optional

After this you can answer: What does CloudFront add in front of the load balancer, and how does the load balancer trust it?

flowchart TB
B[Browser] -->|"HTTPS"| CF["CloudFront edge"]
CF -->|"HTTP 80 + X-Origin-Verify"| ALB["ALB :80"]
ALB -->|"rule 100: header + /trpc/*"| A["api"]
ALB -->|"rule 200: header"| W["web"]
ALB -.->|"no header: 403"| X["refused"]
  1. Edge. The browser talks HTTPS to CloudFront, using the free *.cloudfront.net certificate.
  2. Cache. /_next/static/* (hashed JS and CSS) is cached at the edge. /trpc/* and the pages are not cached.
  3. The security group filters source IPs. alb-sg allows TCP 80 only from the AWS managed prefix list for CloudFront. But every CloudFront distribution, including other people’s, uses those ranges.
  4. The header identifies this distribution. CloudFront adds X-Origin-Verify with a random value. The ALB’s listener forwards only requests that carry it; its default action is a fixed 403.

The distribution is made in infra/src/entry/cdn.ts → createCdn, the HTTP listener with the header rules in infra/src/entry/listener.ts → createListener, and the header value in infra/src/entry/origin-secret.ts → createOriginSecret: 32 random letters and digits, because the ALB reads * and ? in a header condition as wildcards.

Why it is off: a new AWS account may need verification by AWS Support before it can create CloudFront distributions. Until then, MiniTickets runs in ALB-only mode: the ALB serves HTTPS directly with the ACM certificate. Switching modes is done only while entryEnabled is false, because both modes have a listener on port 80.

The web app sends private, no-store on every HTML page, so CloudFront would store no page, even /events.

From → toWhat happensWhat allows it
Browser → CloudFront distributiononly when cdnEnabled = trueHTTPS to the nearest edge location, https://<id>.cloudfront.net
  • certificate the free default *.cloudfront.net certificate; viewerProtocolPolicy redirect-to-httpscreateCdn
CloudFront distribution → Application Load Balanceronly when cdnEnabled = truefetches from the origin over plain HTTP on port 80, adding the X-Origin-Verify header
  • sg-rule alb-sg inbound rule minitickets-dev-alb-in-80: TCP 80 from the AWS managed prefix list com.amazonaws.global.cloudfront.origin-facingcreateSecurityGroups
  • secret origin customHeaders: X-Origin-Verify = the origin secretcreateCdn
Origin secret → CloudFront distributiononly when cdnEnabled = trueCloudFront sends this value as X-Origin-Verify
  • config createOriginSecret().headerValue → createCdn originHeaderValue (a Pulumi secret, never a stack output)createOriginSecret
See CloudFront on the map (switched off) →

CloudFront → Distributions → comment “minitickets dev: HTTPS in front of the ALB” → Behaviors, Origins. In ALB-only mode there is none.

SymptomCauseWhere to look
CloudFront returns 504 for every pagealb-sg has no TCP 80 rule for the CloudFront prefix listalb-sg → Inbound rules
The ALB’s own DNS name returns 403By design: no secret headerListener → default action
Creating the distribution failsThe account must be verified firstThe deploy log (Pulumi error)
Why is the security group alone not enough?

The prefix list says “some CloudFront distribution”. Anyone can create one and point it at our ALB. The header proves it is ours.

What is the risk of caching pages at the edge?

A page with one user’s data could be served to another user if the cookie is not part of the cache key. That is why /trpc/* and pages use CachingDisabled and the web app sends no-store.

What changes for the API when CloudFront is on?

One more proxy stands in front, so the trusted proxy hop count changes. The API must read the real client IP from the right place for rate limits.

Advanced: Behaviors and the hop count

/_next/static/* uses the managed CachingOptimized policy; /trpc/* and the default use CachingDisabled. PriceClass_100 keeps edge locations to the cheaper regions. TRUST_PROXY_HOPS is chosen by the mode in infra/src/entry/mode.ts (1 in ALB-only mode). Comments in infra/src/entry/cdn.ts still describe a 30 s /events cache from before the no-store change; the code wins.

Answer these without scrolling up. If one feels hard, reread that section.

  1. What does CloudFront cache for MiniTickets, and what not?
  2. What are the two locks, and why are both needed?
  3. What does the ALB answer to a request without the header?
  4. Why is CloudFront switched off today?
  5. Why may the header value contain only letters and digits?

Redraw it from memory: browser → CloudFront → ALB :80 with the header, the two rules, and the 403 default

Try this: on the map, turn on 'Show later stages', find the three dashed CloudFront-mode pieces and read why they are off

Read this book online: https://book.theversehub.in/