OpenResty 静态站:缓存策略与安全响应头落地

1534 字
8 分钟
OpenResty 静态站:缓存策略与安全响应头落地

静态博客构建一次、到处分发,表面上很省事;真正上线后,瓶颈往往不在「能不能打开」,而在「打开得快不快、缓存对不对、头信息安不安全」。本文以 OpenResty(兼容 Nginx 配置)托管 Astro/Firefly 产物为例,给出一套可直接落地的缓存与安全响应头方案。

目标拆解#

对静态站通常要同时满足:

  1. HTML 可快速更新:发文后用户刷新即见新内容,不靠硬刷新清缓存。
  2. 静态资源可长期缓存_astro/、字体、图片等带 hash 的文件,浏览器尽量本地复用。
  3. 安全基线:补齐常见响应头,降低点击劫持、MIME 嗅探、协议降级等风险。
  4. 传输更省:开启压缩,对文本资源减体积。

不要把「整站 expires 30d」一刀切——那是踩坑最快的方式。

目录与路由习惯#

Firefly/Astro 构建后常见结构:

dist/
index.html
posts/<slug>/index.html
_astro/*.js|*.css # 文件名含内容 hash
assets/
pagefind/

对应 OpenResty root 指向发布目录(例如 /opt/openresty/www/blog),文章 URL 形如 /posts/<slug>/

推荐 location 分层#

核心思路:按扩展名与路径分流,而不是对 location / 统一缓存。

1. HTML:短缓存或禁用强缓存#

# HTML:允许协商缓存,但禁止长期强缓存
location ~* \.html$ {
add_header Cache-Control "public, max-age=0, must-revalidate" always;
add_header X-Content-Type-Options "nosniff" always;
try_files $uri $uri/ $uri/index.html =404;
}
# 目录型页面(Astro 的 /posts/xxx/)
location / {
add_header Cache-Control "public, max-age=0, must-revalidate" always;
try_files $uri $uri/ $uri/index.html =404;
}

max-age=0, must-revalidate 的含义是:浏览器每次都要向源站确认是否过期;配合后续 ETag/Last-Modified,未改动时仍可 304,流量很小,但发文后能立刻生效。

若前面还有 CDN,可把 HTML 的 CDN 缓存设为 1–5 分钟,并在发布脚本里触发 purge;源站侧仍建议保持「可 revalidate」。

2. 带 hash 的构建产物:一年缓存 + immutable#

location ^~ /_astro/ {
access_log off;
add_header Cache-Control "public, max-age=31536000, immutable" always;
try_files $uri =404;
}
location ^~ /assets/ {
access_log off;
add_header Cache-Control "public, max-age=31536000, immutable" always;
try_files $uri =404;
}

Astro 打包进 _astro/ 的 JS/CSS 文件名含 hash,内容变了文件名就变,旧 URL 不会被错误复用,因此 immutable 是安全的。

3. 字体与图片#

location ~* \.(woff2?|ttf|otf|eot)$ {
access_log off;
add_header Cache-Control "public, max-age=31536000, immutable" always;
add_header Access-Control-Allow-Origin "*" always; # 若跨域用字体再开
}
location ~* \.(png|jpe?g|gif|webp|avif|svg|ico)$ {
access_log off;
add_header Cache-Control "public, max-age=2592000" always; # 30 天,按需求调
}

没有 hash 的图片不要盲目 immutable,否则换图同名会「改不掉」。

安全响应头(站点级)#

server { ... } 内统一加(对错误页也生效时用 always):

server {
listen 443 ssl http2;
server_name blog.example.com;
root /opt/openresty/www/blog;
# 基础安全头
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 纯静态站可先用较宽松 CSP,再逐步收紧
# add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self';" always;
# … ssl、location 等
}

说明:

  • HSTS 只在确认全站 HTTPS、无混用 HTTP 子域后再开。
  • CSP 对带内联脚本/第三方统计的主题容易误伤,建议先在报告模式或分阶段启用。
  • OpenResty/Nginx 的 add_header 在嵌套 location 里会「覆盖」上层,而不是合并——所以关键头要么只在最内层写全,要么用更现代的 more_set_headers(headers-more 模块)统一管理。

压缩#

gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 5;
gzip_types text/plain text/css application/javascript application/json application/xml image/svg+xml;
# 若已编译 brotli 模块
# brotli on;
# brotli_comp_level 5;
# brotli_types text/plain text/css application/javascript application/json application/xml image/svg+xml;

HTML/CSS/JS/JSON/SVG 压缩收益明显;已压缩的 woff2、png、jpg、webp 不必再压。

发布后如何验证#

发文或改配置后,用本地回环 + SNI 验证(端口按你的反代实际调整):

Terminal window
# 看 HTML 是否禁止长期强缓存
curl -skI --resolve blog.example.com:443:127.0.0.1 \
https://blog.example.com/posts/some-slug/
# 看构建资源是否 immutable
curl -skI --resolve blog.example.com:443:127.0.0.1 \
https://blog.example.com/_astro/some-file.hash.js

期望:

  • 文章页:HTTP/2 200Cache-Controlmax-age=0 或短 max-age。
  • _astro 资源:max-age=31536000 且最好带 immutable
  • 安全头:至少能看到 X-Content-Type-OptionsX-Frame-Options 或你配置的等价头。

再配合一次真实浏览器:DevTools → Network,确认二次访问 HTML 走 304/协商,hash 资源走 disk cache。

和构建流水线的配合#

静态站正确缓存的前提是 构建产物文件名稳定且内容寻址

  1. 用主题/框架自带的 hash 资源命名(Astro 默认如此)。
  2. 发布采用「整目录替换」而不是零散覆盖,避免半新半旧。
  3. HTML 不长期强缓存;若用 CDN,发布脚本里加对应 URL 的 purge。
  4. 构建机 OOM 时优先限制并行、提高 swap 或 NODE_OPTIONS=--max-old-space-size=...,而不是关掉 pagefind/字体子集化后留下残缺产物。

示例发布节奏(概念上):

pnpm build → 清空 www → 拷贝 dist → (可选)CDN purge → curl 抽检首页与最新文章

常见坑#

现象可能原因处理
发文后仍看到旧文HTML 被 expires 30d 或 CDN 强缓存HTML 改为 revalidate;purge CDN
改了 CSS 不生效自定义 CSS 无 hash 且长缓存文件名加版本号,或缩短 max-age
安全头「配了但没有」子 location 的 add_header 覆盖了父级在返回 200 的那个 location 写全,或用 headers-more
仅首字节快、整页慢未开 HTTP/2、未压 JS、字体阻塞开 http2、压缩、font-display: swap
本地 curl 200、外网 404反代 root/try_files 与发布目录不一致对齐 root 与实际 dist 拷贝路径

最小可复制清单#

  1. root 指向当前发布目录;try_files 支持 Astro 的目录型 URL。
  2. /_astro/、带 hash 的 assets:max-age=31536000, immutable
  3. HTML 与页面路径:max-age=0, must-revalidate(或 CDN 短 TTL + purge)。
  4. 站点级安全头 +(确认 HTTPS 后)HSTS。
  5. gzip/brotli 覆盖文本类型。
  6. 每次发布后用 curl -I 抽检一篇新文章和一个 _astro 资源。

小结#

OpenResty 托管静态博客,性能与正确性主要来自 分层缓存发布可观测,而不是堆一堆模糊的 expires。HTML 求「新」,hash 资源求「稳」,安全头求「全站一致」。把这三件事写进配置和发布脚本,日更站点就能在「当天可见」和「重复访问足够快」之间取得平衡。

若你的主题还有 service worker 或 PWA,需要额外审查 precache 列表,避免把 HTML 永久钉在客户端——那是另一层「缓存」,比 Nginx 更难排查。纯静态 + 上述头策略,对个人技术博客通常已经够用。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

OpenResty 静态站:缓存策略与安全响应头落地
https://blog.727221.xyz/posts/2026-07-26-openresty-static-cache-headers/
作者
小鱼人
发布于
2026-07-26
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
小鱼人
技术 / 运维 / 随笔。
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
站点统计
文章
4
分类
3
标签
16
总字数
6,057
运行时长
0
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.15.0
文章许可
CC BY-NC-SA 4.0