Files
novalon-website/nginx-static.conf
T
zhangxiang a0328a623f chore(qa): 验收台账与证据入库 + 构建/部署配置同步
- docs/acceptance/qa-tracker.md:跨周期缺陷单一真源台账(§7=第五轮)。
- 周期 1/2 + iPhone SE/axe 验收证据目录、ACCEPTANCE_REVIEW 快照入库。
- 同步 README/CONTEXT/CLAUDE/DESIGN/testing/deployment/lessons-learned 口径;
  next.config/Dockerfile/nginx/Jenkinsfile/docker-compose/sentry/prisma 对齐
  standalone 产物装配与部署形态。
2026-09-28 10:48:09 +08:00

159 lines
7.1 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Novalon 站点边缘配置(HTTP→HTTPS + HSTS + gzip + limit_req + 反代 standalone 应用)
#
# ⚠️ 这是一段 **http 上下文 include 片段**(放进 conf.d/ 或被主配置 include),
# 不是完整的 nginx.conf:它没有 events{}/http{} 外壳。
# 历史缺陷:旧版 Dockerfile 与 docker-compose.yml 把本文件 COPY/MOUNT 到
# /etc/nginx/nginx.conf,而本文件只有裸 server 块 → 直接 nginx -t 即失败;
# 需要完整生产网关(含 gitea / jenkins 虚拟主机)时用 nginx-static-production.conf。
#
# 头部分工(唯一来源,勿在此叠加):
# - 文档级安全头 CSP / X-Frame-Options / X-Content-Type-Options / Referrer-Policy /
# Permissions-Policy / X-XSS-Protection / X-DNS-Prefetch-Control 全部由
# next.config.mjs 的 headers() 随应用一起发布;本文件不再 add_header 这些名字。
# 原因:nginx add_header 不会替换上游同名头部,只做追加,两层各设一次就会让浏览器收到
# `X-Frame-Options: DENY, SAMEORIGIN` 这类非法的重复/互斥值(验收 §5 确认的线上缺陷)。
# - 本文件只保留确实属于边缘的东西:TLS 与 301、HSTS(Next 不发送,且只有终结 TLS 的
# 一层知道协议与证书状态)、gzip、limit_req,以及应用未设置的 Content-Disposition。
# - 页面不再由 nginx 直服静态 HTML:output 为 standalone 后,HTML 由 Next 运行时
# (ISR/SSR/Draft)产出,静态资源与 /uploads/ 仍可由磁盘直供(见下,缺文件即 404)。
limit_req_zone $binary_remote_addr zone=novalon_page:10m rate=50r/s;
upstream novalon_app {
# 应用与 nginx 同机(systemd / 端口发布)时用 127.0.0.1;
# 与 docker-compose.yml 同网络时改为容器名:server novalon-website:3000;
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 80;
server_name novalon.cn www.novalon.cn;
# ACME http-01 challenge 必须留在 80 且不能被跳转吞掉
location /.well-known/acme-challenge/ {
root /usr/share/nginx/html;
try_files $uri =404;
}
location / {
return 301 https://www.novalon.cn$request_uri;
}
}
server {
listen 443 ssl http2;
server_name novalon.cn www.novalon.cn;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 边缘自有头部:Next 不发送 HSTS(它无法得知终结点使用的协议),故只在这里设置
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# 注意:以下 location 均含 add_header,按 nginx 语义不再继承 server 级头部,
# 故每块各自重述 HSTS;应用级头部一律不在此重述(由 next.config.mjs 负责)。
# 预构建产物可用磁盘直服时启用(root 需与部署侧同步目录一致);
# 未配置磁盘副本时删除本块即可,全部流量交给 novalon_app。
location /_next/static/ {
alias /var/www/novalon/_next/static/;
expires 1y;
# Cache-Control 由 Next 官方声明为「immutable 资源不可覆盖」,此处仅补充磁盘直服
# 路径(Next 未应答)所需的缓存语义,与应用头部不冲突、不同名重复。
add_header Cache-Control "public, max-age=31536000, immutable" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
try_files $uri @novalon_proxy;
}
# 本地字体(约束:字体全部本地文件,禁外部 CDN)
location /fonts/ {
alias /var/www/novalon/fonts/;
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable" always;
add_header Access-Control-Allow-Origin "*";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
try_files $uri @novalon_proxy;
}
# 媒体库接口可签发短期签名 URL:需要直取文件时优先走下面 location / 的反代;
# 本块仅在存在磁盘副本(如 CDN 回源目录)时生效。
location ~* \.(jpg|jpeg|png|gif|webp|avif|ico)$ {
root /var/www/novalon;
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
try_files $uri @novalon_proxy;
}
# 上传件:应用自身对 /uploads/ 只发 nosniff/CSP(同源落点风险),
# 文档与压缩包在这里强制下载。Content-Disposition 应用未设置 ⇒ 不构成跨层重复。
location /uploads/ {
limit_req zone=novalon_page burst=20 nodelay;
proxy_pass http://novalon_app;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
location ~* /uploads/.*\.(pdf|zip|docx?|xlsx?|pptx?)$ {
add_header Content-Disposition "attachment" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
}
}
# 页面 / API / 管理后台 / 缺文件的静态资源:交给 Next standalone 运行时,
# 应用头部(含 CSP/XFO)随响应原样透传。
location / {
limit_req zone=novalon_page burst=20 nodelay;
proxy_pass http://novalon_app;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
# 复用 upstream keepalive:Connection 置空。生产 standalone 无 HMR/WebSocket,
# 若将来需要 Upgrade 透传,请像 nginx-static-production.conf 那样加 $connection_upgrade map。
proxy_set_header Connection "";
proxy_hide_header X-Powered-By;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
}
# 静态资源磁盘副本未命中时的回源出口
location @novalon_proxy {
proxy_pass http://novalon_app;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
}
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 256;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml
application/rss+xml
image/svg+xml;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
}