AWS CloudFront skill for Codex

High hit ratios without stale deploys. Codex puts a private S3 bucket or API behind CloudFront with Origin Access Control, splits cache policies from origin request policies, and keeps the cache key down to what actually changes a response. Hashed assets get year-long immutable caching while HTML revalidates, so deploys stop depending on full invalidations, and SPA routing moves into a tested CloudFront Function.

  • Cloud Platforms
  • $aws-cloudfront
  • 176 lines
  • 2 files
  • ~2,100 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- aws-cloudfront

Installs to ~/.agents/skills/aws-cloudfront/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $aws-cloudfront, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Configures Amazon CloudFront distributions - private S3 origins with Origin Access Control, cache policies vs origin request policies, Cache-Control strategy for hashed assets and HTML, CloudFront Functions vs Lambda@Edge, SPA and index.html rewrites, invalidations, response headers policies, signed URLs and ACM certificates in us-east-1. Use when putting a CDN in front of S3 or an API, the cache hit ratio is low, users see stale content after a deploy, S3 returns 403 through CloudFront, or adding redirects, security headers or edge auth.

Use it when

  • Putting a CDN in front of S3, an ALB or API Gateway
  • Cache hit ratio is low or users see stale content
  • S3 returns 403 through CloudFront
  • Adding redirects, rewrites, security headers or signed URLs at the edge

Use something else when

How to check the result

Repeated curl requests show x-cache Hit from cloudfront with the expected Cache-Control, direct S3 requests return 403, client-side routes return HTML 200 and API 404s stay 404.

The mistake it prevents

Mapping 403 and 404 to /index.html with custom error responses, which turns API errors and missing assets into HTML 200 responses.

~/.agents/skills/aws-cloudfront/SKILL.md

--- name: aws-cloudfront description: Configures Amazon CloudFront distributions - private S3 origins with Origin Access Control, cache policies vs origin request policies, Cache-Control… license: MIT ---

Done well means: the origin is reachable only through CloudFront, the cache key contains only what changes the response, immutable assets are cached for a year while HTML revalidates quickly, deploys never need a full /* invalidation to look correct, and the hit ratio is measured rather than assumed.

Before changing anything

  1. Read AGENTS.md and the IaC for the distribution; note how the site is built and deployed (which files are content-hashed, which command uploads them).
  2. Inspect the live distribution (needs network and credentials):
aws cloudfront get-distribution-config --id E123EXAMPLE > dist.json
jq '.DistributionConfig | {aliases:.Aliases.Items, origins:[.Origins.Items[]|{id:.Id,domain:.DomainName,oac:.OriginAccessControlId}], default:.DefaultCacheBehavior|{cache:.CachePolicyId,originReq:.OriginRequestPolicyId,fns:.FunctionAssociations,viewer:.ViewerProtocolPolicy}, behaviors:[.CacheBehaviors.Items[]?|{path:.PathPattern,cache:.CachePolicyId}]}' dist.json
aws cloudfront list-cache-policies --type managed --query 'CachePolicyList.Items[].CachePolicy.[Id,CachePolicyConfig.Name]' --output table

Updating with the raw API requires the current ETag via --if-match; prefer IaC.

S3 origin with Origin Access Control

Keep the bucket private (Block Public Access on) and use OAC, not the legacy Origin Access Identity and not the S3 website endpoint.

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowCloudFrontOAC",
    "Effect": "Allow",
    "Principal": { "Service": "cloudfront.amazonaws.com" },
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::acme-site-prod/*",
    "Condition": { "StringEquals": { "AWS:SourceArn": "arn:aws:cloudfront::111111111111:distribution/E123EXAMPLE" } }
  }]
}
  • Without s3:ListBucket, missing objects return 403, not 404. That is usually what you want (no key enumeration), but map 403 deliberately in error responses.
  • SSE-KMS buckets also need the key policy to allow cloudfront.amazonaws.com to kms:Decrypt with the same AWS:SourceArn condition.
  • In CDK, origins.S3BucketOrigin.withOriginAccessControl(bucket) writes both.

Cache key and forwarding

Two separate policies per behavior:

  • Cache policy: what goes into the cache key (headers, cookies, query strings) and TTLs.
  • Origin request policy: what is forwarded to the origin without being part of the key.
BehaviorCache policyOrigin request policy
Static assets from S3 (/assets/*)Managed-CachingOptimized (658327ea-f89d-4fab-a63d-7e88639e58f6)none, or Managed-CORS-S3Origin if CORS is used
Dynamic API (/api/*)Managed-CachingDisabled (4135ea2d-6df8-44a3-9df3-4b5a84be39ad)Managed-AllViewerExceptHostHeader (b689b0a8-53d0-40ab-baf2-68738e2966ac) for API Gateway/Lambda URL origins
Cacheable API responses varying by a header or query paramCustom cache policy with only those keysForward what the origin needs

Confirm managed IDs with the list-cache-policies command above before hardcoding them.

Every header, cookie or query string in the cache key multiplies cache entries and cuts the hit ratio. Never put Authorization, all cookies or User-Agent into the key of a cacheable behavior. Forwarding the Host header to API Gateway or a Lambda function URL breaks TLS/routing; that is why AllViewerExceptHostHeader exists.

Cache-Control strategy

CloudFront honours origin Cache-Control within the cache policy’s min/max TTL.

ContentHeader at uploadDeploy action
Content-hashed assets (app.3f9c1a.js)public, max-age=31536000, immutableUpload before HTML; never invalidate
HTML, index.html, unhashed manifest filespublic, max-age=0, s-maxage=60 (or no-cache)Upload last; optionally invalidate /index.html
Downloadable files that change in placeShort max-age, or version the URLPrefer new URLs over invalidation
aws s3 sync dist/assets s3://acme-site-prod/assets \
  --cache-control "public, max-age=31536000, immutable"
aws s3 sync dist s3://acme-site-prod --exclude "assets/*" \
  --cache-control "public, max-age=0, s-maxage=60"

Deploying to production and deleting stale files (--delete) need the user’s confirmation; old hashed assets should stay for a while so cached HTML still works.

Invalidations: the first 1,000 paths per month are free, then charged per path; a wildcard counts as one path. A routine /* invalidation on every deploy hides a broken Cache-Control strategy and briefly sends all traffic to the origin.

aws cloudfront create-invalidation --distribution-id E123EXAMPLE --paths "/index.html" "/"

Edge code: Functions vs Lambda@Edge

NeedUse
URL rewrites, redirects, header manipulation, simple token or cookie checks, A/B bucketingCloudFront Functions (viewer request/response, sub-millisecond, very cheap)
Network calls, request body access, origin-request/response events, heavier librariesLambda@Edge (Node.js/Python, deployed in us-east-1, versioned ARN)
Key-value lookups at the edge (redirect maps, feature flags)CloudFront Functions with a KeyValueStore

SPA and directory-index rewrite (viewer request, runtime cloudfront-js-2.0):

function handler(event) {
  var request = event.request;
  var uri = request.uri;
  if (uri.startsWith('/api/')) return request;
  if (uri.endsWith('/')) {
    request.uri = uri + 'index.html';
  } else if (!uri.split('/').pop().includes('.')) {
    request.uri = '/index.html';          // SPA client-side route
  }
  return request;
}

Prefer this over custom error responses that map 403/404 to /index.html with 200: those apply to every behavior, so API 404s turn into HTML 200s, and real missing assets are masked.

Test before publishing:

aws cloudfront test-function --name spa-rewrite --if-match $ETAG \
  --stage DEVELOPMENT --event-object fileb://test-event.json
aws cloudfront publish-function --name spa-rewrite --if-match $ETAG

Publishing pushes to the LIVE stage for every associated distribution; confirm first.

Security and TLS

  • ViewerProtocolPolicy: redirect-to-https, minimum protocol TLSv1.2_2021.
  • ACM certificates for CloudFront must be in us-east-1, whatever the origin region.
  • Add a response headers policy for HSTS, X-Content-Type-Options, Referrer-Policy, X-Frame-Options or CSP rather than setting them in edge code.
  • Private content: signed URLs or signed cookies with a trusted key group (public key in CloudFront, private key in Secrets Manager). Never the root account key pair method.
  • Associate AWS WAF for rate limiting and managed rule groups on public apps.
  • Custom origins (ALB): restrict the ALB security group to the CloudFront managed prefix list and require a secret custom header, or use VPC origins for private ALBs.

Gotchas

  • Changes take a few minutes to deploy to all edge locations; wait for Deployed status (aws cloudfront wait distribution-deployed --id E123EXAMPLE) before testing.
  • DefaultRootObject only applies to /, not subdirectories; use the rewrite function.
  • Behaviors match in order of precedence; /* style defaults catch everything not matched earlier. Put specific patterns first.
  • Query strings are not forwarded or cached by default with CachingOptimized; an app relying on ?v=2 cache busting will get the old file.
  • Lambda@Edge replicas take time to delete after disassociation; deleting the function fails until they are gone.
  • Origin response timeout defaults to 30 seconds; long API calls need a higher value or an async design.

Verification

curl -sI https://www.acme.com/assets/app.3f9c1a.js | grep -iE 'x-cache|age|cache-control'
curl -sI https://www.acme.com/assets/app.3f9c1a.js | grep -i x-cache   # second call: Hit from cloudfront
curl -sI https://acme-site-prod.s3.eu-west-1.amazonaws.com/index.html  # direct S3: 403
curl -sI https://www.acme.com/some/client/route                        # 200, HTML

Check the cache hit ratio in the CloudFront console reports or the CacheHitRate metric (requires additional metrics enabled) over a day of traffic; static-heavy sites should be well above 90%.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "AWS CloudFront"
  short_description: "High hit ratios without stale deploys"
  default_prompt: "$aws-cloudfront Serve our React build from a private S3 bucket through CloudFront with a custom domain"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem