Nginx默认不缓存404响应,proxy_cache_valid 404无效;可行方案是用error_page拦截404并转为可缓存的200响应,或用proxy_store生成404标记文件配合定时清理。

proxy_cache_valid 本身不支持直接为 404 状态码设置独立缓存时间,因为 Nginx 官方文档明确说明:该指令仅对 2xx、3xx 和部分 4xx(如 403、404)响应生效的前提是 proxy_intercept_errors off 且后端真实返回了这些状态码——但关键在于:Nginx 默认不会缓存 404 响应,即使你写了 proxy_cache_valid 404 1s,它也被忽略。
真正可行的方案,是绕过“缓存 404”这个限制,转而让 Nginx 主动拦截 404,并用可缓存的 200 响应替代它,再对该 200 响应做精细缓存控制。以下是两种生产环境验证有效的策略:
✅ 方案一:用 error_page + proxy_cache 将 404 转为可缓存的 200 页面
适用于你想统一返回一个轻量级“页面不存在”提示页(如 /404.html),并防止高频刷 /xxx 这类无效路径。
# 在 http 块中定义共享缓存区(必须)
proxy_cache_path /var/cache/nginx/404_cache levels=1:2 keys_zone=404_cache:10m inactive=5m max_size=100m;
server {
location / {
proxy_pass http://backend;
proxy_intercept_errors on; # 关键:启用拦截
# 拦截后端返回的 404,转交由 @handle_404 处理
error_page 404 = @handle_404;
}
location @handle_404 {
# 返回自定义 404 页面,但状态码是 200(可缓存)
proxy_cache 404_cache;
proxy_cache_valid 200 10s; # ⚠️ 只对 200 生效 → 这里设为 10 秒防刷
proxy_cache_use_stale updating;
proxy_cache_lock on;
# 静态文件方式(推荐,最轻量)
root /usr/share/nginx/html;
try_files /404.html =404;
# 或代理方式(需后端配合返回 200)
# proxy_pass http://static_backend/404.html;
# proxy_set_header Host $host;
}
}
✅ 效果:
- 后端返回 404 → Nginx 拦截 → 内部重定向到
@handle_404→ 返回/404.html并带200 OK→ 被404_cache缓存 10 秒 - 同一 URL 在 10 秒内被刷 100 次,实际只生成 1 次响应,后续全部走缓存
⚠️ 注意:/404.html 文件需真实存在,且权限可读;proxy_cache_valid 此处写的是 200,不是 404。
✅ 方案二:用 proxy_store 实现“按需生成 + 短期文件缓存”
适用于你希望保留原始 404 语义(仍返回 404 状态码),但避免反复向后端查询不存在的路径(例如爬虫扫 /wp-admin.php、/.git/config 等)。
location / {
root /var/www/cache;
# 先查本地是否有缓存文件(含 404 标记文件)
try_files $uri @fetch_404;
# 若文件存在且非 404 标记,则直接返回(200)
# 若是 404 标记文件(如 /404/xxx),则返回 404
location ~ ^/404/(.+)$ {
return 404;
}
}
location @fetch_404 {
internal;
proxy_pass http://backend;
proxy_intercept_errors on;
error_page 404 =200 /404/$uri; # 把 404 转成 200,并存为标记文件
# 关键:保存响应(即使是 404,也存空文件作标记)
proxy_store on;
proxy_store_access user:rw group:rw all:r;
proxy_temp_path /var/tmp/nginx;
# 用空响应体 + 自定义 header 区分
add_header X-Cached-404 "true";
}
然后配合定时清理脚本(如每 30 秒删掉超过 30 秒的 /404/* 文件),实现“404 请求只打一次后端,后续同路径在 TTL 内直接返回 404”。
❌ 不起作用的常见误区
-
proxy_cache_valid 404 1s;→ 完全无效,Nginx 忽略该配置 -
proxy_cache_bypass $upstream_http_x_no_cache;配合后端返回X-No-Cache: 1→ 对 404 无意义,因 404 不进缓存 - 依赖
Cache-Control: no-cache头控制 → 后端返回 404 时,Nginx 根本不读这些头
总结关键点
- Nginx 不缓存 404 响应体,这是设计使然,无法绕过
- 真实防刷要靠:把 404 拦截 → 转成可缓存的 200 响应,或用文件系统标记 + 定时清理
-
proxy_cache_valid的生效对象永远是它实际缓存的状态码(通常是 200/301/302),不是你期望缓存的那个 - 所有缓存策略都需搭配
proxy_cache_lock on防穿透、proxy_cache_use_stale updating保可用性
不复杂但容易忽略。











