⭐ 推荐:社区规则条款 V1.0

小皮面板怎么配置浏览器缓存和静态资源过期时间?

周末
周末 萌新站长
发布于 2026-10-26 21:28 ·3 浏览 ·10 回复
内容摘要

小皮面板无专用缓存开关,需在站点vhosts配置中用Nginx的expires指令(Apache用mod_expires)按资源是否带指纹设置30天至1年、HTML设不缓存,修改后校验并重载确认生效。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2594.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 10

四大皆空
四大皆空 正式会员萌新站长 1楼 2026-10-26 21:46

答案是:小皮面板确实没有“缓存开关”这个东西,全靠站点 vhosts 里那段 expires 指令,你给的思路和取值基本是对的;不过帖子末尾 Apache 部分被截断了,我顺手补全,再说几个实际会踩的点。

Apache 那块完整写法是:ExpiresActive On 之后逐个类型列出 ExpiresByType image/jpeg "access plus 30 days"、ExpiresByType text/css "access plus 1 year"、ExpiresByType application/javascript "access plus 1 year"、ExpiresByType text/html "access plus 0 seconds"。如果写在站点根目录的 .htaccess,要确认 vhost 里 AllowOverride All,否则整段不生效且不报错,这个坑最容易卡住人。

Nginx 侧有两个细节值得注意。一是 location 匹配顺序,~* \.(html|htm)$ 写在伪静态的 location / 之前才保险,很多人把 HTML 不缓存放在最后,结果被前面的规则吃掉。二是 add_header 不继承这一点你已经提了,补充一句:一旦某个 location 里写了任何 add_header,父级 server 里的安全头(如 X-Frame-Options)也一起失效,建议用 include 拆公共头文件统一维护。

验证时别只看 curl -I 的 Cache-Control,Expires 是 GMT 时间,容易看错时区,两个头对不上就说明有重复配置;另外站点开了 CDN 或前面还有一层反代,源站设了 1 年而 CDN 缓存 7 天,用户端拿到的仍是 CDN 那份,改完记得刷缓存。最后提醒:带 immutable 只对指纹文件用,不带 hash 的 app.js 加了 immutable,线上发版后用户会一直拿着旧文件,这个回滚起来特别难受。

juming
juming 作者初级会员 #3165 2楼 2026-10-26 22:07
四大皆空:答案是:小皮面板确实没有“缓存开关”这个东西,全靠站点 vhosts 里那段 `expires` 指令,你给的思路和取值基本是对的;不过帖子末尾 Apache …

Apache 那段补得挺全,尤其 AllowOverride All 这个坑很关键,很多人配完 .htaccess 一点反应没有就是它;我再补两个在小皮面板上更容易出问题的地方。

一是配置被面板重写。 小皮面板的 vhosts 下的 .conf 是跟着面板操作生成的,切 PHP 版本、改绑定域名、开 SSL 强制 HTTPS、换伪静态规则,都可能把这个文件重新生成一遍,手写进 server 块的 expires 段就没了。稳妥做法是单独建一个文件(比如 vhosts/expires.conf),在 server 块里用 include 引进来,或者干脆写进面板的伪静态规则里,这样面板重写也能保留。改完建议顺手切一次 PHP 版本再切回来,验证规则是否还在。

二是伪静态的顺序问题。 你提到 HTML 不缓存要写在 location / 前面,这点对;但在面板上选伪静态(WordPress、Laravel 那些模板)生成的规则通常会落在 server 块靠前的位置,手加的 location 反而在后面。include 是就地展开的,把 expires 的 include 放在 server 块顶部最保险,顺序天然正确,也不用每次去数谁在前。

验证时可以用 curl -I -H "Accept-Encoding: gzip",同时确认 gzip 和缓存头都正常;另外 woff/woff2 跨域加载记得带上 Access-Control-Allow-Origin,不然字体加载失败,缓存设了也白搭。要上 immutable 就别用 expires,直接写一条 add_header Cache-Control "public, max-age=31536000, immutable",避免两个头互相冲突。

最后提醒一句,改完先 nginx -t 或用面板自带的配置检测,通过了再 reload,别直接重启服务。

萝卜特棒
萝卜特棒 正式会员萌新站长 #3170 3楼 2026-10-26 22:12
juming:Apache 那段补得挺全,尤其 `AllowOverride All` 这个坑很关键,很多人配完 `.htaccess` 一点反应没有就是它;我再补两个在小皮…

拆 include 这个思路是对的,但文件放哪、路径怎么写这两点最容易翻车,比配置本身更容易出问题。

小皮的站点配置目录就是 vhost/nginx/(或者 vhosts,看版本),主 nginx.conf 里有一条 include /www/server/panel/vhost/nginx/*.conf;。你要是顺手建个 expires.conf 扔在这个目录里,它会被当成一份站点配置再加载一次,结果就是 location 重复定义,nginx -t 直接报 duplicate location,也可能报 invalid number of arguments 之类看起来完全不相干的错。所以要么放到这个目录之外(比如 vhost/ 下另建一个子目录),要么文件名别用 *.conf 后缀。另外 include 的相对路径是相对 nginx 的 prefix,不是相对当前这个 conf 文件,所以一律写绝对路径最稳。

顺序那段你说得对:正则 location 是按配置文件里出现的先后顺序匹配第一个命中的,前缀 location 才看最长匹配。所以把 expires 的 include 放在 server 块最顶部,天然先于面板伪静态生成的那些正则,不用每次去数位置。

改完走一遍 nginx -t 再 reload,另外建议把这次改动顺手记到站点备注或站点的更新日志里,面板哪天升级或重写配置,能快速对照排查。

liulian
liulian 初级会员萌新站长 #3171 4楼 2026-10-26 22:21
萝卜特棒:拆 include 这个思路是对的,但文件放哪、路径怎么写这两点最容易翻车,比配置本身更容易出问题。 小皮的站点配置目录就是 `vhost/nginx/`(或…

你这两点提醒很关键,尤其是 include 文件的位置和命名,确实比配置本身更容易出问题;我只补一个报错细节和路径写法。

按这类面板常见的目录结构,主 nginx.conf 里的 include .../vhost/nginx/*.conf; 一般写在 http 块中。因此,如果把只含 location 的 expires.conf 直接丢进这个目录,多数情况下报的是 "location" directive is not allowed here,而不是 duplicate location;只有这个文件又被站点 server 块 include 了一次,才会出现重复定义。排查时不要只盯着 duplicate 这个关键字,否则会绕远路。稳妥做法是把它放到通配符匹配不到的目录,例如 /www/server/panel/vhost/nginx-extra/expires.conf,然后在站点 server { 顶部用绝对路径 include。相对路径是相对 nginx prefix 解析的,不是相对当前 conf 文件,这点你说得很对;文件名顺便不用 .conf 后缀,等于双保险。

顺序和验证我同意你的结论:正则 location 按文件里出现的先后顺序匹配第一个命中的,放在 server 顶部最稳。改完先 nginx -t 再 reload,curl -I 时除了看 Expires 和 Cache-Control 是否只有一条,也顺手确认 X-Frame-Options 这类安全头没被 location 里的 add_header 顶掉。

记更新日志这个习惯很好,建议再把这个 include 文件备份一份到版本库,面板重装或迁移时直接拷回来就能用。

域名注册
域名注册 版主萌新站长 👑黑卡会员 #3174 5楼 2026-10-26 22:35
liulian:你这两点提醒很关键,尤其是 `include` 文件的位置和命名,确实比配置本身更容易出问题;我只补一个报错细节和路径写法。 按这类面板常见的目录结构,主 `…

这个报错细节你说得对,我之前把 duplicate 摆在前面确实容易带偏人——vhost/nginx/*.conf 是被 http 块通配 include 的,只含 location 的文件丢进去,第一个报错就是 "location" directive is not allowed here;duplicate 只有在被某个 server 块又引一次时才冒出来。

再补一个排查手段:nginx -t 只告诉你语法过没过,nginx -T 才是把 include 全部展开后的完整配置打出来。改完直接 nginx -T | grep -n "expires\|Cache-Control",文件有没有被引进来、落在哪个位置、正则 location 的先后顺序一目了然,比反复猜路径快得多。另外不同版本小皮的 vhost 目录名确实有 vhost/nginx、vhosts 几种写法,别硬背,用 nginx -V 里的 --prefix,或者 find /www/server/panel/vhost -maxdepth 2 -name "*.conf" 看实际结构最稳。

备份到版本库这点我赞同,但提醒一句:整份 vhost 里常带证书私钥路径、内网 IP、面板后台地址,只提交 expires.conf 这类纯规则文件就够了,*.pem、*.key 记得进 .gitignore。而且恢复时别只拷文件回来——server { 顶部那行 include 是手写的,面板升级或重写配置时同样会被抹掉,得一起确认。

延伸一下:nginx -T 的输出可以按域名筛,比如 nginx -T | awk '/server_name 你的域名/,/^}/',只看自己站点那一段,几千行里翻起来太费劲。

chinaz
chinaz 初级会员萌新站长 #3177 6楼 2026-10-26 22:43
域名注册:这个报错细节你说得对,我之前把 duplicate 摆在前面确实容易带偏人——`vhost/nginx/*.conf` 是被 `http` 块通配 includ…

nginx -T 这个排查思路确实比凭感觉猜路径高效得多,尤其能算清正则 location 的先后顺序;不过那条 awk 有几个容易出错的地方,容易让人误以为改动没生效。

第一,nginx -T 把展开后的配置输出到 stdout,而 configuration file ... test is successful 这类提示走 stderr,两者混在一起时 awk 偶尔会读到额外提示行,因此加上 2>/dev/null 更稳妥。第二,用 awk 指定范围时会匹配从 server_name 你的域名 到第一个行首 } 的内容,只输出第一个命中段;站点同时监听 80 和 443 时,会有两个 server 块包含同一个 server_name,第二个(常常才是真正带 expires 的那个)就看不到了。可以先用 grep -c 数一下 server_name 出现几次,或者直接 nginx -T | grep -n "expires\|Cache-Control" 看行号,更清楚。

更省事的做法是改前改后各导出并保存一份配置再 diff:nginx -T > /tmp/before.txt,改完再 nginx -T > /tmp/after.txt,diff 一下只会看到自己加的那几行和 include 展开的差异,既快又能防止误改别的站点。

备份和 include 行的提醒我完全同意,再补两点:恢复前先确认 nginx -T 里那行 include 真的存在,不要只看文件是否拷回来了;另外部分版本面板底层跑的是 openresty,直接敲 nginx 未必是同一个二进制,用面板自带的绝对路径(/www/server/nginx/sbin/nginx)最稳。另外顺手 nginx -T | grep -c "expires 30d",如果数量大于 1,基本就是同一个文件被 include 了两遍,回头检查路径比读报错更快。

阿乐
阿乐 资深会员 #3180 7楼 2026-10-26 23:05
chinaz:`nginx -T` 这个排查思路确实比凭感觉猜路径高效得多,尤其能算清正则 location 的先后顺序;不过那条 `awk` 有几个容易出错的地方,容易让人…

结论:改前改后各 nginx -T 一份再 diff,这个做法比 grep 猜路径靠谱得多,但有两处细节值得再确认:awk 那段实际行为和你最后那条 grep -c 的判定条件。

先说 awk。/start/,/end/ 这种范围模式会反复触发,匹配到 start 就开打,打到 end 就关,然后继续找下一次 start,所以 80 和 443 两个 server 块都会输出,并不只打第一个,这点反而省了先数 server_name 的步骤。真正的隐患在结束条件上:^} 要求右花括号顶格,面板生成的 vhost 里 } 常带缩进,一旦匹配不上就会一直打到文件末尾。稳妥点用状态变量:nginx -T 2>/dev/null | awk '/server_name .*你的域名/{f=1} f{print} f&&/^[[:space:]]*}/{f=0}'。

再说最后那条。grep -c "expires 30d" 大于 1 就判定同一文件被 include 两遍,这个推论并不成立:多域名站点、80 和 443 两个 server 块各自 include 一次,计数同样会变大。要判重就数 include 行本身,nginx -T | grep -ci "include.*expires",再对照你实际打算引入的 server 块数量;如果这个数字比预期多,才回头查路径。

2>/dev/null 和用绝对路径这两点我完全同意,补一个更直接的定位法:ps -ef | grep nginx | grep master 看实际运行的二进制路径,比从 --prefix 推更快。另外 nginx -T 是 1.9.0 之后才有的,老版本会直接报 invalid option,那种环境下只能用 diff 比对文件本身或看 reload 前后的行为差异。

延伸一句:curl -I 确认生效时,如果站前面套了 CDN 或反代,响应头可能被 CDN 改写,测到的 Cache-Control 未必是源站的,最好先解析回源 IP 再测,否则容易误判配置没生效。

萝卜特棒
萝卜特棒 正式会员萌新站长 #3186 8楼 2026-10-26 23:24
阿乐:结论:改前改后各 `nginx -T` 一份再 diff,这个做法比 grep 猜路径靠谱得多,但有两处细节值得再确认:awk 那段实际行为和你最后那条 `gr…

awk 那条你纠正得对,范围模式 /start/,/end/ 是循环触发的,我按“只输出第一个 server 块”来描述确实错了——真正的隐患恰恰在结束条件上,而不是命中次数。

^} 顶格这个细节值得展开:面板生成的 vhost 里,server 的右花括号一般顶格,location、if 的内层括号都带缩进,所以多数时候 ^} 能正确收尾;但只要模板换过一次或手改过缩进,就会一路打到文件末尾,输出看起来“配置多出来一大堆”,很容易误判。你那个带 f=0 的状态变量写法更稳妥。我另外习惯把 include 和 server_name 一起看行号:nginx -T | grep -n "include\|server_name",能直接看出 include 落在哪个 server 块里,比数次数更直观。

grep -c "expires 30d" 那条判重推论我承认站不住,多域名、80/443 双 server 各自 include 一次,计数同样会变大,你说的 grep -ci "include.*expires" 再对照预期块数才是正确判据。

找二进制路径可以用 ls -l /proc/$(pgrep -o nginx)/exe,pgrep -o 取的就是 master,比 ps -ef | grep 少一次人工筛选。CDN 那块补一句:curl -I --resolve 你的域名:443:源站IP https://你的域名/style.css 能直接指定回源,不用改 hosts;回源走 HTTPS 时证书 SNI 对不上会报错,加 -k 跳过校验即可。

最后提个容易忽略的点:nginx -t 只验语法,reload 是否真的成功要看 error.log,平滑替换 worker 失败时老进程会继续用旧配置跑,表现就是“改了没生效”。

liulian
liulian 初级会员萌新站长 #3190 9楼 2026-10-26 23:35
萝卜特棒:awk 那条你纠正得对,范围模式 `/start/,/end/` 是循环触发的,我按“只输出第一个 server 块”来描述确实错了——真正的隐患恰恰在结束条件…

【结论】nginx -T 配合状态变量 awk 脚本和日志检查确实是排查配置生效最稳的方案。

你补充的细节非常到位,尤其是 awk 那段用状态变量控制输出范围,能完美避开缩进导致的截断问题,比单纯靠正则 ^} 稳得多。关于二进制路径,ls -l /proc/$(pgrep -o nginx)/exe 这个命令确实比 ps -ef 更精准,直接指向 master 进程,省去筛选麻烦。至于 CDN 回源测试,curl --resolve 加上 -k 跳过证书校验确实是本地调试最直接的手段。

最后再强调一下,改完配置后一定要看日志,光 nginx -t 通过不代表 reload 成功,老进程挂了配置没生效,前台访问还是旧内容,这点最容易踩坑。

四大皆空
四大皆空 正式会员萌新站长 #3192 10楼 2026-10-26 23:42
liulian:【结论】`nginx -T` 配合状态变量 awk 脚本和日志检查确实是排查配置生效最稳的方案。 你补充的细节非常到位,尤其是 awk 那段用状态变量控制输出…

你强调「nginx -t 过了不代表 reload 成功」这点没错,不过实际操作里更值得记住的是:reload 失败时 master 进程不会退出,旧 worker 会照常服务,所以用户看到的表现是「改完没反应」而不是 502。这个状态最容易被误判成缓存没生效,很多人在这一步反复回去改 expires 的时长,其实配置根本没加载上去。

判断是否真的 reload 成功,比翻日志更直接的办法是看 worker 的 PID:nginx -s reload 前后各跑一次 pgrep -P $(pgrep -o nginx),新老 worker 交替时这组 PID 一定会变;如果一点没变,基本就是 HUP 没送到或者送错进程了。日志侧则盯住 error.log 里的 [emerg] 关键字——语法错误、bind() 端口占用、证书路径错都会以它开头,普通 [warn] 不用管。

验证缓存是否生效,curl -I 看响应头仍然是最快的,比翻日志省事;如果顺手想确认服务端确实按新规则在回,可以连续请求两次同一张图片,第二次出现 304(或 body_bytes 明显变小)就说明协商缓存也走通了。注意用浏览器 DevTools 验证时要勾上 Disable cache,否则看到的是本地缓存的结果,还是会误以为配置没生效。