Caching 404s and Redirects at the Edge
Static site caching guides focus on the responses that matter to readers: HTML and assets. The responses readers never want — 404s and redirects — often get no caching attention at all, and on a site with years of history they can be a surprising share of traffic. Vulnerability scanners probe for /wp-login.php and /.env thousands of times a day; old links from forums point at URLs from two migrations ago; bookmarks and search results still carry pre-migration paths that now redirect. If none of those responses is cached, every one of them travels to the origin.
This guide caches 404s and redirects at the CDN edge for a static site on CloudFront with an S3 origin and on Cloudflare, sets lifetimes that do not slow down fixes, and measures the change in origin load. It is part of CDN Caching Rules for SSGs. The site is a 2,300-page documentation site with 3,100 redirects from two migrations.
Prerequisites
- CDN logs or analytics grouped by status code and cache status.
- A custom 404 page in the build output.
- A redirect list, ideally generated from data as in Keeping Redirects Working After an SSG Migration.
Step 1: Measure What Reaches the Origin
A week of CloudFront logs grouped by status and edge result showed where origin requests came from:
| Response | Share of all requests | Share of origin requests |
|---|---|---|
| 200 HTML and assets | 81% | 38% |
| 404 | 11% | 44% |
| 301/302 | 7% | 17% |
| other | 1% | 1% |
404s were 11% of traffic but 44% of origin requests, because CloudFront's default error caching minimum TTL was set to 0 in this distribution and the S3 origin sent no cache headers on errors. Redirects, served by S3 website routing rules, were uncached for the same reason. Together they accounted for 61% of the origin load on a "fully cached" static site.
Step 2: Cache 404s Briefly
On CloudFront, error caching is configured per distribution with custom error responses. Serve the site's 404 page with a 404 status and cache the error for five minutes:
"CustomErrorResponses": { "Quantity": 2, "Items": [
{ "ErrorCode": 404, "ResponsePagePath": "/404.html", "ResponseCode": "404", "ErrorCachingMinTTL": 300 },
{ "ErrorCode": 403, "ResponsePagePath": "/404.html", "ResponseCode": "404", "ErrorCachingMinTTL": 300 }
]}
The 403 mapping matters for S3 with origin access control: a missing object returns 403 from S3, which should appear to readers as a 404.
On Cloudflare, a Cache Rule can set status-code TTLs: cache 404 responses for 300 seconds at the edge, and do not let browsers cache them (browser TTL: respect origin, with the origin sending Cache-Control: no-store on 404s, or a Transform Rule setting it). Browsers should not remember 404s, because a reader who hits a page seconds before it is published should get it on refresh.
Five minutes was the balance point: long enough that a scanner requesting /wp-login.php every few seconds from many IPs hits the cache almost every time, short enough that a newly published page is never hidden for long — and deploys purge the cache anyway.
Step 3: Move Redirects to the Edge and Cache Them
S3 website routing rules redirect at the origin, so every redirect is an origin round trip, and S3 website endpoints do not support HTTPS or origin access control. The better design is redirects at the edge: a CloudFront Function with the redirect map, as in CloudFront Functions for Redirects, or Cloudflare Bulk Redirects. Edge redirects never reach the origin, so caching them is moot.
Where redirects must stay at the origin — an nginx server, a stub page, a Worker that computes them — add cache headers to the redirect response itself:
location = /docs/installation.html {
add_header Cache-Control "public, max-age=3600, s-maxage=2592000" always;
return 301 /guide/install/;
}
s-maxage=2592000 lets the CDN keep the redirect for 30 days; max-age=3600 keeps browsers from caching it permanently. Permanent redirects cached for a long time in browsers cannot be undone, which matters if a redirect turns out to be wrong.
Step 4: Handle Scanners Before They Reach the Cache
A large share of the 404s came from a small set of paths probed by vulnerability scanners: /wp-admin/, /xmlrpc.php, /.env, /.git/config, /phpmyadmin/. For a static site these can be answered at the edge without even a cache lookup. On Cloudflare, a WAF custom rule blocks or challenges requests matching those patterns; on CloudFront, the CloudFront Function that handles redirects returns a 404 for them directly. That removed 72% of all 404 requests before caching was involved, and made the 404 reports described in Logging 404s at the Edge far more useful, because they now showed real broken links instead of scanner noise.
Step 5: Keep Fixes Fast
Caching errors and redirects raises one worry: a fix — a new page at a URL that used to 404, a corrected redirect — must not be hidden by a cached response. Two practices keep that from happening. The deploy pipeline purges the cache for changed HTML and for redirect-map changes, as usual. And the lifetimes above are chosen so that even without a purge, a stale 404 disappears in five minutes and a stale redirect is corrected at the edge by a purge, with browsers holding it for at most an hour.
Soft 404s and Stub Pages
Some static sites implement redirects as stub HTML pages with a <meta http-equiv="refresh"> tag, generated by plugins such as jekyll-redirect-from or Hugo aliases. These return 200, so the CDN caches them like any page, but they cost a full HTML download plus a second navigation for every visitor, and search engines treat them as weaker signals than a real 301. Where the host supports edge redirects, generate a redirect map from the same data instead of stub pages and delete the stubs; on this site that removed 3,100 tiny HTML files from the build and made each old URL resolve in one hop. Also make sure the 404 page itself is lightweight: it is served to every scanner and broken link, and a 404 page that loads the full site search bundle multiplies that cost.
Measured Impact
| Measure | No error or redirect caching | After |
|---|---|---|
| Origin requests per day | 96,000 | 31,000 |
| 404 requests reaching origin | 42,000/day | 1,300/day |
| Redirect requests reaching origin | 16,000/day | 0 (edge function) |
| Median time for a redirect, EU readers | 140 ms | 18 ms |
| S3 request and egress cost per month | 44 USD | 15 USD |
| Scanner requests reaching the cache layer | ~30,000/day | ~800/day |
Pitfalls & Rollback
- Long browser caching of 301s. Browsers keep them indefinitely; limit
max-ageor you cannot fix mistakes. - Caching 404s without purging on deploy. New pages may briefly 404 for readers who hit the URL early.
- Returning 200 for the 404 page. A custom error page must keep the 404 status, or search engines index it.
- Caching 5xx for long. A brief origin error cached for minutes becomes an outage.
- Rollback: error caching TTLs and cache rules are distribution settings; setting them back to 0 or disabling the rule restores the previous behaviour immediately.
Conclusion
On a static site with history, errors and redirects can outnumber real page requests at the origin. Cache 404s briefly at the edge but not in browsers, move redirects into an edge function or give them long edge lifetimes, and answer scanner probes before they reach the cache. On this site that removed two thirds of origin requests and made redirects nearly instant.
FAQ
Should 404 responses be cached?
Yes, for a short time. Bots and broken links generate many requests for missing URLs, and without caching each one reaches the origin. A negative cache time of a few minutes removes almost all of that load while still letting a newly published page appear quickly.
How long should redirects be cached?
Permanent redirects from a migration can be cached at the edge for a long time, since they rarely change, but keep the browser cache lifetime moderate, because a 301 cached in a browser cannot be purged. Temporary redirects should have short or no caching.
Why are redirects reaching the origin at all on a static site?
When redirects are implemented at the origin, for example by an S3 website routing rule, an nginx rewrite or a stub HTML page with a meta refresh, the CDN forwards each request unless the redirect response itself carries cache headers.
Does caching 404s risk hiding newly published pages?
Only for the length of the negative cache time, and only if someone requested the URL before it existed. Purging the cache on deploy removes cached 404s along with everything else, so new pages appear immediately after a deploy that purges.
Related
- Parent: CDN Caching Rules for SSGs — the complete caching setup.
- Tiered Caching and Origin Shield — reducing the misses that remain.
- Purging the CDN Cache After a Static Deploy — clearing cached errors on deploy.
- Logging 404s at the Edge — turning real 404s into fixes.
- Testing Redirects and Headers Before Deploy — checking redirect cache headers.
Filed underHow-tosAWS (S3 and CloudFront)