准备好经过校验的静态构建产物 public/ 后,线上托管层面的目标是保持精简:不运行动态语言解释器或数据库,仅用一个只读挂载的 nginx:1.27-alpine 容器处理 HTTPS 卸载与静态文件分发。
本文整理生产环境使用的 compose.yaml 与 Nginx 站点配置,涵盖只读卷挂载、分级缓存头、真实 404 状态码配置以及安全响应头核验。
宿主机与 Nginx 容器的挂载边界
在服务器上,将与博客托管相关的文件统一放在 /srv/blog(或自定义 <站点根目录>)下,并按读写需求区分只读挂载(:ro)与读写挂载(:rw):
| 宿主机路径 | 容器内映射路径 | 挂载权限 | 用途说明 |
|---|---|---|---|
/srv/blog/releases/current |
/usr/share/nginx/html |
:ro(只读) |
托管当前生效版本的静态网页,容器内 nginx worker 进程无权修改文件 |
/srv/blog/conf/default.conf |
/etc/nginx/conf.d/default.conf |
:ro(只读) |
站点路由、压缩与安全响应头配置,防止运行时配置被改动 |
/srv/blog/certs |
/etc/nginx/certs |
:ro(只读) |
挂载由 acme.sh 签发的 TLS 证书与私钥 |
/srv/blog/logs |
/var/log/nginx |
:rw(读写) |
持久化访问日志与错误日志,供宿主机 logrotate 按日切分 |
为什么不要在 try_files 末尾直接写 /404.html
配置静态站点的 location / 时,如果把 try_files 的最后一个参数直接写成 /404.html,会导致错误的 HTTP 状态码:
try_files会按顺序检查前几个参数对应的文件或目录是否存在于root下,只有当前面的候选路径都不存在时,才会回退到最后一个参数;- 如果最后一个参数写的是内部 URI
/404.html,而站点根目录下恰好存在404.html文件,那么当请求一个不存在的链接(如/posts/non-existent-slug/)时,Nginx 会执行内部重定向并返回404.html的页面内容; - 由于内部重定向查找成功,Nginx 最终返回给客户端的 HTTP 状态码会变成
200 OK而非404 Not Found。
这种「页面显示 404 内容、状态码却是 200」的现象称为软 404(Soft 404),会让搜索引擎把失效链接当作正常页面收录,也会干扰监控探针的判断。
正确的写法是将「返回 404 状态码」与「指定 404 错误页面」拆成两条指令:
1 | # 路径与目录均不存在时返回 HTTP 404 状态码,再交由 error_page 渲染 /404.html |
Nginx 站点配置(/srv/blog/conf/default.conf)
结合 Gzip 文本压缩、分级 Cache-Control 缓存策略与安全响应头,在 /srv/blog/conf/default.conf 中编写站点配置:
1 | server { |
关于
add_header的继承规则:在 Nginx 中,一旦某个子location块定义了自己的add_header(例如Cache-Control),它就不会再自动继承上层server块里的add_header。因此在每个细分location中显式写出安全响应头(或抽取为include snippets/security-headers.conf;),才能保证所有静态资源都带有X-Content-Type-Options: nosniff。
编写 Docker Compose 文件并启动容器
在 /srv/blog/compose.yaml 中固定 nginx:1.27-alpine 镜像版本,并配置内存上限与自动重启策略:
1 | services: |
首次启动前,先将软链接 /srv/blog/releases/current 指向已同步的静态版本目录,随后启动容器并检查配置语法:
1 | # 1. 创建或更新指向最新静态产物版本目录的软链接 |
容器托管与 HTTP 状态码核对表
服务启动后,可在服务器终端用 curl -I 核对以下四项响应指标:
| 检查场景 | 验证命令 | 预期输出 |
|---|---|---|
| 首页正常响应 | curl -sI http://127.0.0.1/ | head -n 10 |
返回 HTTP/1.1 200 OK,含 X-Content-Type-Options: nosniff |
| 文章固定链接 | curl -sI http://127.0.0.1/posts/01-blog-planning-architecture/ |
返回 HTTP/1.1 200 OK |
| 真实 404 状态码 | curl -sI http://127.0.0.1/posts/not-found-page/ |
返回 HTTP/1.1 404 Not Found(而非 200 OK) |
| 容器只读挂载验证 | docker exec blog-web sh -c "touch /usr/share/nginx/html/test" |
返回 Read-only file system,确认只读挂载生效 |