- 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 产物装配与部署形态。
159 lines
7.1 KiB
Plaintext
159 lines
7.1 KiB
Plaintext
# 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;
|
||
}
|