Sometimes you don’t want to block traffic at your origin.
You want to stop it at the edge.
Maybe you’re decommissioning a site and you need everything to return a 404 immediately. Maybe you’ve discovered a path being hammered and you want a hard, deterministic response while you investigate. Or maybe you’re spinning up a “sink” distribution that should never serve content.
CloudFront Functions are perfect for this: lightweight JavaScript that runs at CloudFront edge locations on viewer events, with extremely low latency.
This post shows a CloudFront Function that always responds with 404 Not Found—no matter what URL, headers, cookies, query strings, or method the browser sends.
Why a CloudFront Function (and not Lambda@Edge)?
CloudFront Functions are built for ultra-fast request manipulation at the edge.
They’re ideal when you need:
- A simple, deterministic response
- Minimal logic
- No origin fetch (save bandwidth and load)
- Something you can deploy quickly
If you need heavier logic (network calls, more complex auth, larger libraries), Lambda@Edge might be the right fit. But for “always return a 404,” a CloudFront Function is the cleanest tool.
The Function: Always Return 404
Create a new CloudFront Function and paste this code:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function handler(event) { return { statusCode: 404, statusDescription: "Not Found", headers: { "content-type": { value: "text/plain; charset=utf-8" }, "cache-control": { value: "no-store, no-cache, max-age=0, must-revalidate" }, "pragma": { value: "no-cache" }, "expires": { value: "0" } }, body: "404 Not Found\n" }; } |
This does exactly what it says:
- Responds with 404
- Sends a small plaintext body
- Adds headers to discourage browsers/proxies from caching the response
Where to Attach It
Attach the function to your distribution’s behavior on the Viewer Request event.
Why Viewer Request?
- The function runs before CloudFront attempts to fetch from the origin.
- That means your origin never sees the request.
- It’s the fastest path to a deterministic response.
In CloudFront:
- Open your distribution
- Go to Behaviors
- Edit the behavior you want to affect (often the default behavior)
- Under Function associations, set:
- Viewer request → your “always-404” function
- Save
Once the change propagates, every request handled by that behavior returns 404.
Optional: Return an HTML 404 Page
If you want something friendlier than plaintext, switch the content type and body:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function handler(event) { return { statusCode: 404, statusDescription: "Not Found", headers: { "content-type": { value: "text/html; charset=utf-8" }, "cache-control": { value: "no-store, no-cache, max-age=0, must-revalidate" }, "pragma": { value: "no-cache" }, "expires": { value: "0" } }, body: "<!doctype html><html><head><meta charset='utf-8'><title>404 Not Found</title></head><body><h1>404 Not Found</h1><p>The requested resource does not exist.</p></body></html>" }; } |
A Few Practical Notes
- Scope matters: The function only applies to the behaviors you associate it with.
- Testing: Use curl to confirm quickly:
curl -i https://your-domain.example/anything
- Caching: A 404 can be cached by CloudFront depending on your cache settings. Returning
no-storehelps on the client side, but CloudFront caching is controlled by cache policy and TTLs.- If you want CloudFront itself to never cache this, make sure your behavior’s cache policy/TTLs match your intent.
When This Pattern Is Useful
- Temporary “kill switch” while an origin incident is in progress
- Decommissioning an origin without needing to keep it online
- Hard-blocking legacy paths that should never be served again
- Reducing load during abuse investigations (before you implement a finer rule)
If you later decide you want a 301/302 redirect, 410 Gone, or a 403 instead, you can reuse this exact pattern by changing the status code and body.




