AWS CloudFront skill for Claude Code
Private origins, small cache keys, high hit ratios. Claude puts S3 behind Origin Access Control with the bucket fully private, splits traffic into per-path behaviors, and keeps cache keys limited to what changes the response while forwarding the rest through origin request policies. Assets get long immutable TTLs, HTML stays fresh, edge logic starts with CloudFront Functions, and every change is verified with x-cache headers and hit-ratio metrics.
- name
aws-cloudfront - size236 lines · ≈2,800 tokens
- files1
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Configures Amazon CloudFront distributions - origins for S3 with Origin Access Control, ALB, API Gateway and VPC origins, cache behaviors by path, managed and custom cache policies that keep the cache key small, origin request and response headers policies, TTLs versus origin Cache-Control, CloudFront Functions versus Lambda@Edge, SPA routing, signed URLs and cookies, ACM certificates in us-east-1, invalidations versus versioned assets, logging, and cost controls. Use when putting a CDN in front of S3 or an API, when CloudFront returns 403 or stale content, when the cache hit ratio is low, or when choosing edge compute. Try asking
- Serve our React SPA from a private S3 bucket through CloudFront with proper routing
- CloudFront returns 403 AccessDenied for our site. Find out why
- Our CloudFront cache hit ratio is 40%. Fix the cache policies
Use it when
- Putting CloudFront in front of S3, an ALB, API Gateway or a Lambda URL
- Getting 403, 502, 504 or stale content from a distribution
- Designing cache and origin request policies per path
- Choosing between CloudFront Functions and Lambda@Edge
Skip it when
- You need bucket policies, presigned uploads or lifecycle rules — use s3-object-storage
- You are designing Cache-Control headers independent of a CDN — use http-caching
How to check the result
Direct S3 requests return 403, the second request for an asset shows x-cache Hit from cloudfront, HTML changes appear within the intended TTL, and the hit ratio for static behaviors exceeds 90%.
The common mistake it prevents
Forwarding all cookies or headers in the cache key so every request becomes a cache miss, then trying to fix freshness with wildcard invalidations.
A distribution is done when the S3 bucket is private and reachable only through CloudFront, each path pattern has a behavior with a cache key containing only what changes the response, static assets are versioned and cached for a year, dynamic paths forward exactly what the origin needs, and the cache hit ratio is measured.
Ground rules
- S3 origins use Origin Access Control (OAC) with Block Public Access on. Never make the bucket public or use the legacy OAI for new work.
- Cache key = only the headers, cookies and query strings that change the response. Everything else the origin needs goes in the origin request policy (forwarded, not keyed).
- Prefer content-hashed filenames over invalidations. Invalidate HTML entry points only.
- Certificates for CloudFront must be in ACM in
us-east-1. - Changing behaviors, origins or the default root on a production distribution affects all traffic within minutes; show the diff and get confirmation. Disabling or deleting a distribution needs explicit confirmation.
Workflow
- [ ] 1. Inspect the distribution: origins, behaviors, policies, aliases, cert
- [ ] 2. Lock down origins (OAC for S3, custom header or VPC origin for ALB)
- [ ] 3. Define behaviors per path with cache and origin request policies
- [ ] 4. Set TTL strategy with origin Cache-Control headers
- [ ] 5. Add edge logic only where needed (Functions first)
- [ ] 6. Security: headers policy, WAF, signed URLs where required
- [ ] 7. Deploy, wait, verify with curl headers and hit-ratio metrics
1. Inspect
aws cloudfront list-distributions --query 'DistributionList.Items[].{id:Id,domain:DomainName,aliases:Aliases.Items,status:Status}'
aws cloudfront get-distribution-config --id E123ABC > dist.json # keep ETag for updates
aws cloudfront list-cache-policies --type managed --query 'CachePolicyList.Items[].CachePolicy.{id:Id,name:CachePolicyConfig.Name}'
aws cloudfront list-origin-request-policies --type managed --query 'OriginRequestPolicyList.Items[].OriginRequestPolicy.{id:Id,name:OriginRequestPolicyConfig.Name}'
Commonly used managed policies (confirm IDs with the commands above):
| Policy | Use for |
|---|---|
Cache: CachingOptimized (658327ea-f89d-4fab-a63d-7e88639e58f6) | Static assets; no cookies/headers/query in key; gzip+brotli |
Cache: CachingDisabled (4135ea2d-6df8-44a3-9df3-4b5a84be39ad) | APIs and personalized pages |
Origin request: AllViewerExceptHostHeader | API Gateway/Lambda URL origins that need headers, cookies, query but reject the viewer Host |
Origin request: CORS-S3Origin | S3 with CORS |
Response headers: SecurityHeadersPolicy | Baseline HSTS, nosniff, frame options, referrer policy |
2. Origins
S3 with OAC: create an OAC (signing behavior “always”, SigV4), attach it to the origin,
use the bucket’s REST endpoint (bucket.s3.eu-west-1.amazonaws.com, not the website
endpoint), then allow the distribution in the bucket policy:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowCloudFrontOAC",
"Effect": "Allow",
"Principal": { "Service": "cloudfront.amazonaws.com" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::acme-site/*",
"Condition": { "StringEquals": { "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E123ABC" } }
}]
}
SSE-KMS buckets also need the KMS key policy to allow cloudfront.amazonaws.com to
kms:Decrypt with the same SourceArn condition.
| Origin | Lock-down |
|---|---|
| S3 | OAC + bucket policy above; Block Public Access on |
| Public ALB | Secret custom origin header checked by an ALB listener rule, plus SG allowing the CloudFront managed prefix list |
| Private ALB/NLB/EC2 | CloudFront VPC origins (no public exposure) |
| API Gateway / Lambda function URL | Lambda URL: OAC with AWS_IAM auth; API GW: custom header or WAF |
Origin settings: HTTPS only to origin, timeouts that match the origin (default 30 s read), Origin Shield in the region nearest the origin for high fan-in or expensive origins.
3. Behaviors
Order matters: first matching path pattern wins; default * is last.
| Path pattern | Origin | Cache policy | Origin request policy |
|---|---|---|---|
/assets/* (hashed files) | S3 | CachingOptimized | none |
/api/* | ALB/API | CachingDisabled (or custom short TTL keyed on needed query strings) | AllViewerExceptHostHeader or custom |
/images/* | S3 or image service | custom: key on w, q, format query only | none |
* (HTML) | S3 | custom: default TTL 0-60 s, honor origin headers | none |
Custom cache policy example (key on two query strings, no cookies):
aws cloudfront create-cache-policy --cache-policy-config '{
"Name": "images-wq", "MinTTL": 0, "DefaultTTL": 86400, "MaxTTL": 31536000,
"ParametersInCacheKeyAndForwardedToOrigin": {
"EnableAcceptEncodingGzip": true, "EnableAcceptEncodingBrotli": true,
"HeadersConfig": { "HeaderBehavior": "none" },
"CookiesConfig": { "CookieBehavior": "none" },
"QueryStringsConfig": { "QueryStringBehavior": "whitelist", "QueryStrings": { "Quantity": 2, "Items": ["w", "q"] } }
}}'
Forwarding all cookies or Authorization/User-Agent in the cache key effectively
disables caching. Keying on Accept for image format negotiation is fine; on
User-Agent almost never.
4. TTL strategy
CloudFront uses origin Cache-Control/Expires clamped by the policy’s Min/Max TTL;
Default TTL applies only when the origin sends none.
| Content | Origin header |
|---|---|
| Hashed assets | Cache-Control: public, max-age=31536000, immutable |
| HTML entry points | Cache-Control: public, max-age=0, s-maxage=60 (or no-cache + revalidate) |
| API responses | Cache-Control: private, no-store unless deliberately cacheable |
| Errors | Error caching minimum TTL per status (default 10 s); lower for 5xx on APIs |
Set headers at upload: aws s3 sync dist/ s3://acme-site/ --cache-control "public,max-age=31536000,immutable" --exclude "*.html"
then upload HTML separately with the short policy. See http-caching for header design.
5. Edge logic
| Need | CloudFront Functions | Lambda@Edge |
|---|---|---|
| URL rewrite, redirects, header add/remove, simple auth token check | Yes (default) | Overkill |
| Runs on | Viewer request/response only | Viewer and origin request/response |
| Network calls, body access, heavy libraries | No | Yes |
| Latency / cost | Sub-millisecond, cheapest | Higher, per-request + duration |
| Deploy region | Global | Author in us-east-1, replicated |
| Config/data lookup | CloudFront KeyValueStore | Any AWS SDK call |
SPA routing with a viewer-request Function (instead of mapping 403/404 to index.html, which also hides real errors from APIs and assets):
// cloudfront-js-2.0
function handler(event) {
var req = event.request;
var uri = req.uri;
if (uri.startsWith('/api/')) return req;
if (uri.endsWith('/')) { req.uri = uri + 'index.html'; }
else if (!uri.includes('.')) { req.uri = '/index.html'; }
return req;
}
aws cloudfront create-function --name spa-rewrite --function-config '{"Comment":"SPA","Runtime":"cloudfront-js-2.0"}' --function-code fileb://spa.js
aws cloudfront test-function --name spa-rewrite --if-match <ETag> --event-object fileb://test-event.json
aws cloudfront publish-function --name spa-rewrite --if-match <ETag>
6. Security
- Viewer protocol policy
redirect-to-https; minimum TLS policyTLSv1.2_2021or newer. - Response headers policy for HSTS, CSP (custom), and CORS when needed.
- AWS WAF web ACL (created in us-east-1 for CloudFront) with managed rule groups and rate-based rules for login and API paths.
- Private content: signed URLs or signed cookies with a key group (public key uploaded to CloudFront, private key in Secrets Manager); short expiry.
- Geo restriction only for legal requirements; it is not access control.
7. Deploy and verify
aws cloudfront update-distribution --id E123ABC --if-match <ETag> --distribution-config file://dist-config.json # confirm first
aws cloudfront wait distribution-deployed --id E123ABC
curl -sI https://www.acme.com/assets/app.3f2c1a.js | grep -iE "x-cache|age|cache-control|via"
curl -sI https://www.acme.com/assets/app.3f2c1a.js | grep -i x-cache # second call: Hit from cloudfront
curl -sI https://acme-site.s3.eu-west-1.amazonaws.com/index.html | head -1 # expect 403 (bucket private)
Invalidations when unavoidable:
aws cloudfront create-invalidation --distribution-id E123ABC --paths "/index.html" "/"
The first 1,000 invalidation paths per month are free, then charged per path; a
wildcard such as /* counts as one path but empties the whole cache and spikes origin
load.
Metrics: enable additional metrics for cache hit rate and origin latency; target > 90% hit ratio for static behaviors. Standard logs (to S3, CloudWatch Logs or Firehose) for analysis; real-time logs only when needed.
Debugging
| Symptom | Cause | Fix |
|---|---|---|
| 403 AccessDenied XML from S3 | OAC missing, bucket policy SourceArn wrong, KMS key policy, or object missing (S3 returns 403 for missing keys without ListBucket) | Fix policy; check the key exists |
| 403 from CloudFront on custom domain | Alias not on the distribution or cert mismatch | Add alternate domain with matching ACM cert in us-east-1 |
| 502 | Origin TLS failure (cert name mismatch, self-signed), origin unreachable | Origin domain must match its cert; check SG and prefix list |
| 504 | Origin slower than origin read timeout | Fix origin or raise timeout |
| Stale content after deploy | Long TTL on unversioned files | Version assets; short TTL on HTML; targeted invalidation |
| Low hit ratio | Cookies/headers/query strings in cache key | Narrow cache policy; move to origin request policy |
| API gets wrong Host / signature errors | Viewer Host forwarded to API Gateway or Lambda URL | AllViewerExceptHostHeader |
Gotchas
- Using the S3 website endpoint as origin forces a custom origin and prevents OAC.
- Default root object applies only to
/, not subdirectories; use a Function for/docs/->/docs/index.html. - Distribution changes take minutes to deploy globally;
wait distribution-deployedbefore testing. - Lambda@Edge logs land in CloudWatch in the region of the edge location that ran it.
- Compression only happens when the cache policy enables gzip/brotli and the object is between 1 KB and 10 MB with a compressible content type.
- Flat-rate pricing plans (introduced late 2025) bundle CloudFront with WAF and other features per distribution; compare against pay-as-you-go for your traffic before switching.
Output
**Distribution:** E123ABC (www.acme.com, ACM us-east-1, TLSv1.2_2021)
**Origins:** S3 acme-site via OAC (bucket private, verified 403 direct); ALB via VPC origin
**Behaviors:** /assets/* CachingOptimized; /api/* CachingDisabled + AllViewerExceptHostHeader; * HTML 60 s
**Edge:** CloudFront Function spa-rewrite (viewer request)
**Security:** SecurityHeadersPolicy + CSP; WAF managed rules + rate limit on /api/login
**Verified:** x-cache Hit on assets; hit ratio 96% (static) after 24 h; HTML updates visible in 60 s About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.