Skip to content

CloudFront origin group: successful 200 from secondary origin (Lambda Function URL) after 404 failover is not cached at the edge

0

I'm seeing unexpected CloudFront caching behavior with an origin group used for on-demand image resizing. After a cold request that fails over from S3 (404) to a Lambda Function URL (200), CloudFront returns the image to the viewer but does not cache it. The next request goes back to origin (S3, now that Lambda has written the object), and only the third request is a cache hit.

This seems to contradict the origin group documentation, which states that when CloudFront sends a request to a secondary origin, "the response behavior is the same as for a CloudFront origin that's not in an origin group" — including caching successful responses per the cache policy.

Architecture

  • Distribution: custom domain, single origin group resized with members [s3, lambda]
  • Failover criteria: 403, 404
  • Resized path pattern: /_/w{width}/* → target origin group resized
  • Primary origin: S3 bucket (OAC)
  • Secondary origin: Lambda Function URL (OAC, AWS_IAM auth)
  • Cache policy: custom policy with MinTTL/DefaultTTL/MaxTTL = 31536000; no cookies/query strings/headers in cache key; EnableAcceptEncodingGzip/Brotli = true
  • Behavior: compress: true, methods GET/HEAD
  • Custom error responses: 403 and 404 with empty ResponsePagePath/ResponseCode, ErrorCachingMinTTL: 60

Cold flow (resized object does not exist in S3 yet):

  1. CloudFront tries S3 → 404
  2. CloudFront fails over to Lambda → generates WebP, writes to S3, returns 200 with Cache-Control: public, max-age=31536000, immutable
  3. Viewer receives 200, but X-Cache: Miss from cloudfront

Expected behavior

After step 3, a second identical request to the same edge location should be X-Cache: Hit from cloudfront.

Actual behavior

RequestX-CacheOriginNotes
1MissLambdax-amzn-requestid present; no server: AmazonS3
2MissS3server: AmazonS3; object now exists (written by Lambda in req 1)
3HitS3age > 0

All three requests hit the same POP (x-amz-cf-pop: EZE50-P7), same URL, same Accept-Encoding: identity.

Control test (S3-only behavior, no origin group)

For a path routed directly to S3 (e.g. /favicon.ico), after invalidation:

  • Request 1: Miss (S3)
  • Request 2: Hit (S3)

So normal caching works; the extra miss appears specific to the origin-group failover path.

Definitive test: Lambda response is not cached

After a cold request that returned 200 from Lambda, I deleted the S3 derivative and immediately re-requested the same URL:

  • Request 1: Miss, Lambda invoked (x-amzn-requestid present)
  • Delete S3 object _/w640/.../file.webp
  • Request 2: Miss, Lambda invoked again (new x-amzn-requestid)

If CloudFront had cached the Lambda response, request 2 should have been a cache hit with no origin invocation. Instead it failed over to Lambda again (S3 404 → Lambda).

Lambda response headers (request 1)

HTTP/2 200
content-type: image/webp
content-length: 65338
cache-control: public, max-age=31536000, immutable
x-amzn-requestid: ...
x-amzn-remapped-content-length: 65338
x-cache: Miss from cloudfront

Body size matches Content-Length. No no-store, no-cache, or private directives.

S3 response headers (request 2, after object exists)

HTTP/2 200
content-type: image/webp
cache-control: public, max-age=31536000, immutable
server: AmazonS3
etag: "..."
last-modified: ...
x-cache: Miss from cloudfront

Request 3 is then Hit from cloudfront.

Questions

  1. Is this expected behavior for origin groups when failover is triggered by 404 (not 5xx)?
  2. Should a successful 200 from the secondary origin be cached at the edge on the first request?
  3. Could CustomErrorResponses with ErrorCachingMinTTL: 60 for 404 interfere with caching the subsequent successful failover response?
  4. Is there a known limitation when the secondary origin is a Lambda Function URL (custom origin + OAC)?

Environment

  • Region: sa-east-1
  • CloudFront distribution with OAC for both S3 and Lambda origins
  • Reproducible consistently across multiple widths and images after S3 object deletion + CloudFront invalidation

Happy to provide distribution ID, request IDs (x-amz-cf-id, x-amzn-requestid), or a minimal CloudFormation/Terraform repro if helpful.

Thanks for any guidance on whether this is a bug, undocumented limitation, or misconfiguration on my side.

2 Answers
0

Hello.

You might want to check the CloudFront standard logs.
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html

By the way, would it be possible for you to share the CloudFormation template or similar resources used to reproduce the issue?

EXPERT

answered 2 months ago

-1

Based on the behavior you're describing, this appears to be working as designed, though I understand it's counterintuitive. Here's what's happening:

When CloudFront receives a 404 from your primary S3 origin, it caches that 404 response at the edge according to your error caching settings (ErrorCachingMinTTL: 60 seconds). Even though CloudFront then fails over to your Lambda secondary origin and receives a successful 200 response, both responses are cached separately - the 404 from S3 and the 200 from Lambda.

However, here's the critical part: CloudFront's request routing logic checks the primary origin first on subsequent requests. Since the 404 from S3 is cached (for 60 seconds), CloudFront will continue to serve from that cached error state and fail over to Lambda again, rather than serving the cached 200 response from Lambda. This explains why your second request still shows a Miss - CloudFront is checking S3 first, finding the cached 404, then failing over.

Once your Lambda function writes the object to S3 and the error cache expires (after 60 seconds), subsequent requests will find the object in S3 and cache that response normally. This is why your third request becomes a Hit - by then, the 404 error cache has likely expired, CloudFront finds the object in S3, and caches it.

The key issue is the interaction between error caching and origin group failover behavior. When you configure custom error responses with ErrorCachingMinTTL, CloudFront caches the error status codes (404, 403) for that duration. During this caching period, CloudFront's behavior with origin groups means it will continue to see the cached error from the primary origin and trigger failover, rather than serving a previously cached successful response from the secondary origin.

To improve this behavior, you could:

  1. Reduce ErrorCachingMinTTL to a very low value (even 0-1 seconds) for 404 errors, so the cached error expires almost immediately after Lambda writes the object to S3.

  2. Remove the custom error response configuration for 404 if you don't need custom error pages, allowing CloudFront to use its default error caching behavior.

  3. Consider a different architecture where Lambda writes to S3 first, then returns a redirect to the S3 URL, though this adds latency.

The behavior you're seeing with Lambda Function URLs as a secondary origin is consistent with how CloudFront handles any custom origin in an origin group - it's not specific to Lambda Function URLs. The documentation stating that "response behavior is the same as for a CloudFront origin that's not in an origin group" is accurate for the caching of the successful response itself, but doesn't account for how the cached error from the primary origin affects subsequent request routing.

This is expected behavior given your configuration, not a bug or undocumented limitation, though it does create the caching inefficiency you've observed.
Sources
How CloudFront processes HTTP 4xx and 5xx status codes from your origin - Amazon CloudFront

answered 2 months ago

  • This makes no sense. CF shouldn't cache the 404 of the primary origin when there's failover and the secondary succeeds.

    Tried disabling CustomErrorResponses. Same issue still occurs.

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.