群晖 NAS 自定义 Nginx 配置:绕开模板,优雅加反代

random-pic-api

前言

在群晖上整点”高级”的网络配置 —— 比如加个反向代理、换个监听端口、写条 URL Rewrite —— 我第一反应都是:

直接去改 /etc/nginx/nginx.conf 不就好了?

然后就被坑了。具体坑法是:配置当场生效,重启一次就没了

我当时还以为是自己改错了文件权限或者 reload 命令不对,反复核对了好几次,最后才反应过来 —— 群晖的 Nginx 配置根本不是一个”手写”的文件,而是开机时用模板生成出来的。你改得再仔细,系统一重启就用模板重新写一份,你的改动被原样冲掉。

这篇把整个机制说清楚,再给一个我自己一直在用的”不碰模板、不丢配置“的做法。

群晖 Nginx 的配置是怎么来的

先 SSH 上群晖看一眼:

1
2
sudo -i
ls /usr/syno/share/nginx/

能看到一堆 .mustache 文件:

1
2
3
4
WWWService.mustache
WWW_Main.mustache
DSM.mustache
... (还有十几个)

这就是群晖 Nginx 配置的”源头”。DSM 每次启动(或者你在 WebStation、控制面板里动配置)的时候,它会读取这些模板、结合当前设置生成最终的 /etc/nginx/nginx.conf 和一堆 conf.d/*.conf

所以有个很重要的结论:

凡是模板能生成出来的东西,你手动改的都会被冲掉。

这个”默认跳转 80 → 5000”的逻辑就是典型,它是在 WWWService.mustache 里写死的。如果你真的非要动它(比如把 80 换成别的端口、或者干掉那个自动 rewrite),那只能改模板本身:

1
2
3
4
5
# 示例:改 WWWService.mustache 里的 80 端口
vim /usr/syno/share/nginx/WWWService.mustache

# 或者注释 WWW_Main.mustache 里的 rewrite 段,阻止重定向
vim /usr/syno/share/nginx/WWW_Main.mustache

改完重启 nginx 或者直接重启 NAS:

1
synoservice --restart nginx

但说真的,我非常不建议动这两个文件。DSM 升级的时候会覆盖它们,WebStation 重装或者打补丁的时候也会把你的改动吃掉。改一次以为搞定,半年后升一次系统又打回原形,还很难 debug。

除非你确实要改”群晖本体监听的端口”这种事,否则有更好的办法。

不碰模板的正确姿势

nginx.conf 会看到最后这几行:

1
2
3
4
5
6
http {
...
include conf.d/http.*.conf;
include app.d/server.*.conf;
include sites-enabled/*;
}

这三行就是给用户留的”后门”。模板每次重新生成 nginx.conf 都会把它们保留着,只要我们在这三个目录里放自己的 .conf 文件,无论是 DSM 重启、WebStation 重装还是系统升级,都不会把我们的配置干掉。

三个目录分别用来干什么

目录文件名规则位于 nginx.conf 中的位置适合放什么
/etc/nginx/conf.d/http.*.confhttp {}全局指令:mapupstreamlimit_req_zone
/etc/nginx/app.d/server.*.confhttp {}群晖自家应用(DSM、套件)的附加 server 配置
/etc/nginx/sites-enabled/随意http {} 段末尾用户自己的 vhost / 反代,首选放这里

我的建议:

  • sites-enabled/ 是你应该优先用的目录,文件名没有规则限制,可读性高,管理方便;
  • app.d/ 最好别碰,那是群晖套件自己在用的;
  • conf.d/ 只有在你要写 map / upstream 这种”全局级”指令时才用,比如写跨域的 Origin 白名单。

/usr/local/etc/nginx//etc/nginx/ 是什么关系

如果你到处翻系统,会发现还有一个 /usr/local/etc/nginx/ 目录,里面也有 conf.dsites-enabled。那是什么东西?

实测下来,它们其实是软链到 /etc/nginx/

1
2
3
ls -la /usr/local/etc/nginx/
# conf.d -> /etc/nginx/conf.d
# sites-enabled -> /etc/nginx/sites-enabled

所以改哪边都一样。我一般直接在 /etc/nginx/sites-enabled/ 下操作,路径更短。

一个完整的实例:反代 Home Assistant

我家 Home Assistant 跑在群晖的 Docker 里,占着 8123 端口。我想让它能通过 ha.dong4j.site 这个域名直接访问,而不是每次都要记 IP + 端口。

/etc/nginx/sites-enabled/ 下新建一个文件:

1
vim /etc/nginx/sites-enabled/home-assistant.conf

内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server {
listen 80;
server_name ha.dong4j.site;

# HomeAssistant 的 WebSocket 需要这两个头
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;

# 保留真实 IP / 协议
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;

location / {
proxy_pass http://127.0.0.1:8123;
proxy_read_timeout 3600s; # WebSocket 保活,HA 有一堆长连
}
}

测试一下配置:

1
nginx -t

没报错就 reload:

1
nginx -s reload

搞定。浏览器直接访问 http://ha.dong4j.site 就能进 Home Assistant 了,DSM 完全不知道发生了什么,它还以为自己在好好跑 WebStation。

同样的思路能干的事

这套路几乎能覆盖所有”我想让群晖 Nginx 帮我干点事”的场景:

  • 反向代理 Docker 里的任意服务:Portainer、Grafana、Vaultwarden、Homepage …
  • URL Rewrite:把静态 HTML 改成伪静态路由;
  • 限流 / IP 白名单limit_req_zone(放 conf.d/http.xxx.conf)+ limit_req
  • 统一 CORS 头map $http_origin $cors_header + add_header

踩坑记录

顺手记几个我自己踩过的点。

坑 1:文件名不符合规则,include 不到

conf.d/app.d/ 这两个目录的 include 语句是 带 glob 的

1
2
include conf.d/http.*.conf;
include app.d/server.*.conf;

意味着如果你在 conf.d/ 下建了个 myvhost.conf不是 http.myvhost.conf),永远不会被加载,还找不到原因。我第一次就在这儿翻过车,查了半天以为是语法不对。

sites-enabled/ 的 include 是 *,没有这个限制,所以首选放这里。

坑 2:改完没 reload 看起来没生效

群晖上 reload 用这两个命令都可以:

1
2
3
nginx -s reload
# 或者
synoservice --restart nginx

前者是 nginx 标准做法,不会断开已有连接;后者会重启整个 nginx 服务(会有几秒的短暂 502)。我一般 reload 够用就用前者。

坑 3:DSM 升级会不会丢?

我亲身经历过从 DSM 7.1 升到 7.2,以及 7.2 的几次小版本更新。结论:

  • /etc/nginx/sites-enabled/ 里的自定义文件 —— 没丢
  • /etc/nginx/conf.d/http.xxx.conf —— 没丢
  • 手动改过的 nginx.confmustache 模板 —— 被覆盖
  • WebStation 卸载重装之后,sites-enabled/ 也依然在。

所以只要你坚持”往 include 目录里加文件”这条原则,升级基本无忧。

坑 4:端口被 DSM 占了

群晖默认把 80 跳转到 5000443 跳转到 5001,这两个端口是被 DSM 占用的。如果你的 server 想监听 80(像我上面那个例子那样),要么:

  • 让家里的路由器把公网的 80 端口映射到群晖的 80,交给我们自己的 sites-enabled 里的 server 处理;
  • 或者直接用非标端口,比如 8080 / 8443,避开冲突。

我自己是让 Cloudflare 回源端口走非标端口的,这样群晖自带的 DSM 跳转一点都不受影响。这部分可以看 [[6b13f395|另一篇关于 Cloudflare CDN 的改造]]。

最后聊两句

写完这篇发现一个挺有意思的事:群晖的 Nginx 其实已经把该留的后门都留好了,只是文档里没人强调

如果你也在群晖上折腾反向代理,希望这篇能让你少走两段弯路 —— 不要改 nginx.conf,也不要改 .mustache 模板,找 include 的目录、建自己的 .conf 文件,这条路最稳。


参考