Immich 跑在 2283,Vaultwarden 在 8222,Jellyfin 8096,Alist 5244,qBittorrent 8081,Portainer 9000。

六个端口,六个 IP:端口,我自己都记不住,更别说朋友了。他每次问我"那个看剧的地址是啥来着",我都得翻一下笔记。

还有个大问题:除了 Vaultwarden 强制要 https,其他都是 http。手机在外面连,运营商有时候会劫持 http 页面,弹广告。

这篇讲我怎么用 Nginx Proxy Manager 把这些服务统一收进 https 域名。

NPM 是干什么的

一句话:给不会写 nginx 配置的人用的反代面板。

网页上填几个空,就能把一个域名反代到内网端口,证书自动申请、自动续期。

它不是必须要用,但对不想天天改配置文件的人来说,省事。

一、先看端口冲突

NPM 要占 80 和 443 才能申请证书、才能正常反代。

我这台机器上,宝塔面板已经占了 80 和 443(主站 moxiaoa.com 就在上面跑)。

两种做法:

做法一:让 NPM 接管 80/443,主站也交给它管。

最干净。把宝塔站的 nginx 停掉,主站的反代规则搬到 NPM 里,之后所有域名统一在 NPM 上维护。

代价是主站要重新配一遍,而且宝塔面板的证书、伪静态这些都要跟着改。

做法二:主站不动,NPM 换端口,中间加一层转发。

我用的这个。原因是我主站已经跑了一个月,ssl 证书、301 规则、伪静态都在宝塔上配好了,搬过去风险大于收益。

具体是:

多了一跳,但主站零改动。

如果你机器上没有别的 web 服务占 80/443,直接用做法一,比我这套简单十倍。 我这是历史包袱,不是推荐方案。

下面按做法一(干净场景)写配置,最后一节说我这种复杂情况的处理。

下面按"机器上没有其他服务"的干净场景讲,最后再说我这种复杂情况的处理。

二、compose 配置

services:
  npm:
    container_name: nginx-proxy-manager
    image: jc21/nginx-proxy-manager:latest
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "81:81"
    volumes:
      - /opt/docker/npm/data:/data
      - /opt/docker/npm/letsencrypt:/etc/letsencrypt
    environment:
      - TZ=Asia/Shanghai
    mem_limit: 512m

三个端口的分工:

端口用途
80HTTP,也是 Let's Encrypt 验证用的
443HTTPS
81管理后台

启动后访问 http://服务器IP:81,默认账号:

第一次登录会强制改密码,改完进主界面。

三、加一个反代

点 Proxy Hosts → Add Proxy Host,填四个地方:

Details 页:

字段填什么
Domain Namesjelly.你的域名.com
Schemehttp
Forward Hostname / IP容器名或者宿主机内网 IP
Forward Port8096
Block Common Exploits勾上
Websockets Support勾上(Vaultwarden、Jellyfin 都需要)

Forward Hostname 填什么,是这套东西的关键。

如果 NPM 和其他容器在同一个 docker 网络里,直接填容器名,比如 jellyfin。

如果不在同一个网络,填宿主机内网 IP,比如 192.168.1.50。

我一开始填了 localhost,结果反代一直 502——因为在 NPM 容器里,localhost 指的是容器自己,不是宿主机。

正确的做法:要么把 NPM 和其他服务放进同一个 docker 网络,要么填宿主机 IP。

我后来统一用了一个自定义网络:

docker network create apps

然后在每个服务的 compose 里加:

networks:
  default:
    external: true
    name: apps

这样 NPM 和服务之间直接用容器名通信。

SSL 页:

点保存,NPM 会自动去 Let's Encrypt 申请证书。

四、证书申请失败的四种原因

这一步是最容易卡住的,我踩了三种。

原因一:DNS 没生效

域名要提前解析到服务器 IP,用 dig 或者 nslookup 确认。刚解析的域名要等几分钟到几小时。

dig +short jelly.你的域名.com

能返回你的服务器 IP 才去申请。

原因二:80 端口不通

Let's Encrypt 的 HTTP 验证要走 80 端口。如果 80 被防火墙拦了、或者被别的程序占了,验证就失败。

云服务器的安全组要放行 80 和 443。

原因三:申请次数限制

Let's Encrypt 同一个域名一周最多失败 5 次,超了要等一周。所以别反复试,先排查清楚再点。

我第一天不知道这个限制,失败三次之后不敢点了,第二天改好 DNS 才成功。

原因四:域名有 CAA 记录限制

少见,但遇到过。如果域名的 DNS 里配了 CAA 记录,只允许特定 CA 签发,Let's Encrypt 就会被拒。查一下:

dig CAA 你的域名.com

五、我实际的配置

我的主站(moxiaoa.com)已经在宝塔的 nginx 上跑着,占着 80/443,不想动。

所以我的方案是:

这样主站不受影响,子域名由 NPM 管理证书。

配置大概长这样:

server {
    listen 80;
    server_name jelly.moxiaoa.com vault.moxiaoa.com photo.moxiaoa.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name jelly.moxiaoa.com vault.moxiaoa.com photo.moxiaoa.com;

    ssl_certificate /etc/letsencrypt/live/子域名/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/子域名/privkey.pem;

    location / {
        proxy_pass https://127.0.0.1:8443;
        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_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

说实话,这套配置比直接用 NPM 麻烦,但因为我主站在宝塔上,改来改去风险更大,就这样了。

如果你的机器是干净的,直接用 NPM 接管 80/443,比我这套简单十倍。

六、几个实用功能

1. 访问列表(Access List)

给不想公开的服务加个 HTTP Basic Auth:

Access Lists → Add → 填用户名密码 → 在 Proxy Host 里选择。

Portainer、qBittorrent 这种管理面板,加上一层,安全很多。

2. 自定义 location

Advanced 页可以加自定义 nginx 配置。比如给 Jellyfin 加 WebSocket 长连接超时:

proxy_read_timeout 3600s;
proxy_send_timeout 3600s;

3. 重定向规则

老域名跳新域名、http 跳 https,都能在界面上配。

七、踩坑提醒

坑一:改完配置要等几秒

NPM 改完配置会重新加载 nginx,大概 2-5 秒。改完立刻访问可能还是旧的。

坑二:镜像更新会重置配置吗

不会,配置在 /data 里。但大版本更新前一定要备份 /data 和 /letsencrypt,我见过更新后证书全丢的案例。

坑三:Let's Encrypt 证书 90 天到期

NPM 会自动续期,但前提是 80 端口一直通。如果哪天你把 80 关了,续期就失败。建议每月上去看一眼证书状态。

最后

装完 NPM,我朋友终于不用记端口了。他现在记的是三个地址:

这才像个正经的家庭服务器。

下一篇写 Uptime Kuma——让这些服务挂了的时候,有人通知我。

---

如果你也在折腾这套服务,碰到卡住的地方,可以加我微信 lige0552 聊聊。白天要上班,消息回得慢,看到都会回。