Nginx 网关别只会反代Docker 部署 Nginx Proxy Manager给家庭服务加一层安全边界家里小主机跑的服务一多端口很快就乱了面板一个端口、监控一个端口、下载器一个端口、文档工具一个端口。为了图省事有人会把每个服务端口都映射出去甚至把 NAS 后台、Docker 管理面板、数据库端口一起暴露到公网。这不是“方便”这是把家里的管理入口摊开给公网看。更稳的做法是在家庭服务器前面加一层统一网关。内部服务继续跑在各自容器里对外只露出必要入口需要临时外网演示或访问时再用 cpolar 暴露这个入口而不是把每个服务端口逐个打出去。这篇用 Docker 部署 Nginx Proxy Manager下面简称 NPM再准备一个whoami示例服务做反代测试。最后用 cpolar 临时映射 NPM 的演示入口并把管理后台和真正的管理类服务留在内网。Nginx Proxy Manager 是什么Nginx Proxy Manager 可以理解为“带 Web 管理界面的 Nginx 反向代理”。它适合放在家庭服务器、小主机、NAS 旁边统一管理多个 Web 服务入口。它能帮你做几件事给多个内部服务配置统一入口例如grafana.home、tools.home、whoami.home把外部访问转发到内部容器、局域网 IP 或指定端口在 Web 界面里管理 Proxy Host不用每次手写 Nginx 配置给服务配置 HTTPS 证书给入口加 Access List、Basic Auth、IP 访问控制处理 WebSocket 转发例如面板、终端、实时监控页面常用到的连接。要注意NPM 不是安全银弹。它只是把入口收拢起来并提供更清晰的访问控制位置。真正的边界仍然来自只暴露必要服务、关闭默认账号、强密码、日志审计、用完关闭临时隧道。本文最终结构本文会搭出这样一条链路外部临时访问者 ↓ cpolar 公网地址临时暴露 8080 ↓ 家庭服务器NPM 代理入口 8080 ↓ Docker 内部网络whoami 示例服务 80同时保留一个关键边界NPM 管理后台 81 端口只绑定 127.0.0.1不公开到公网也就是说外部只访问被你允许的业务入口不访问 NPM 管理后台。环境约定本文命令在 Linux 服务器上执行适用于常见 Ubuntu、Debian、树莓派系统和多数 x86/ARM 小主机。已经安装 Docker 和 Docker Compose 插件。先确认 Docker 可用docker version docker compose version本文使用这些端口用途宿主机端口说明NPM HTTP 代理入口8080后面用 cpolar 临时映射这个端口NPM HTTPS 代理入口8443给真实域名 HTTPS 场景使用NPM 管理后台127.0.0.1:81只允许本机访问不公开whoami 示例服务不映射宿主机只在 Docker 内部网络访问如果你的服务器 8080 已被占用可以把 Compose 文件里的8080:80改成其他宿主机端口例如18080:80。后续 curl 和 cpolar 命令也要同步改端口。第一步创建 NPM 项目目录先创建目录sudo mkdir -p /opt/npm-gateway cd /opt/npm-gateway sudo mkdir -p data letsencrypt mysql目录用途/opt/npm-gateway/data # NPM 配置数据 /opt/npm-gateway/letsencrypt # 证书数据 /opt/npm-gateway/mysql # MariaDB 数据第二步编写 Docker Compose 文件在/opt/npm-gateway下创建docker-compose.ymlcd /opt/npm-gateway sudo tee docker-compose.yml /dev/null EOF services: npm-db: image: jc21/mariadb-aria:latest container_name: npm-db restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: npm_root_password_change_me MYSQL_DATABASE: npm MYSQL_USER: npm MYSQL_PASSWORD: npm_db_password_change_me volumes: - ./mysql:/var/lib/mysql networks: - npm-net npm: image: jc21/nginx-proxy-manager:latest container_name: npm restart: unless-stopped depends_on: - npm-db ports: - 8080:80 - 8443:443 - 127.0.0.1:81:81 environment: DB_MYSQL_HOST: npm-db DB_MYSQL_PORT: 3306 DB_MYSQL_USER: npm DB_MYSQL_PASSWORD: npm_db_password_change_me DB_MYSQL_NAME: npm DISABLE_IPV6: true volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt networks: - npm-net whoami: image: traefik/whoami:latest container_name: whoami-demo restart: unless-stopped networks: - npm-net networks: npm-net: name: npm-net EOF这份 Compose 做了三件事启动 NPM启动 MariaDB 保存 NPM 配置启动whoami示例服务用来测试反代。这里没有把whoami的端口映射到宿主机它只能在npm-netDocker 网络里被访问。NPM 后面会通过容器名whoami-demo转发到它。第三步启动服务并检查容器状态执行cd /opt/npm-gateway sudo docker compose up -d查看容器sudo docker compose ps正常会看到npm、npm-db、whoami-demo都处于 running 状态。再看日志sudo docker logs --tail80 npm sudo docker logs --tail80 npm-db确认宿主机端口监听sudo docker port npm输出里应包含80/tcp - 0.0.0.0:8080 443/tcp - 0.0.0.0:8443 81/tcp - 127.0.0.1:81这个结果很关键81只绑定在127.0.0.1说明 NPM 管理后台没有直接暴露到局域网和公网。第四步初始化登录 NPM并立刻修改默认账号在服务器本机打开http://127.0.0.1:81如果你是在另一台电脑上操作服务器可以用 SSH 本地端口转发打开管理后台ssh -L 8181:127.0.0.1:81 useryour-server-ip然后在本机浏览器访问http://127.0.0.1:8181NPM 默认账号是Email: adminexample.com Password: changeme第一次登录后按页面提示立刻修改管理员邮箱管理员昵称管理员密码。密码建议至少 16 位包含大小写字母、数字和符号不要复用 NAS、路由器、邮箱、Git 平台的密码。修改完成后退出再用新账号登录一次确认默认账号已经不能继续使用。第五步添加 Proxy Host反代 whoami 示例服务进入 NPM 后台打开Hosts → Proxy Hosts → Add Proxy Host在 Details 页填写Domain Names: whoami.local.test Scheme: http Forward Hostname / IP: whoami-demo Forward Port: 80 Cache Assets: Off Block Common Exploits: On Websockets Support: On说明一下这里的关键点Domain Names是访问这个入口时使用的域名Forward Hostname / IP填容器名whoami-demo因为 NPM 和 whoami 在同一个 Docker 网络Forward Port填80这是 whoami 容器内部服务端口Websockets Support建议打开后续反代面板、监控、终端类应用时能少踩坑Block Common Exploits打开可以拦截一部分常见恶意请求。保存后在服务器上测试curl -H Host: whoami.local.test http://127.0.0.1:8080/能看到类似输出Hostname: whoami-demo IP: 127.0.0.1 IP: 172.20.0.3 RemoteAddr: 172.20.0.2:xxxxx GET / HTTP/1.1 Host: whoami.local.test这说明访问已经经过 NPM再转发到了 whoami 容器。第六步给入口加访问控制或 Basic Auth家庭服务不要默认裸奔。即使只是工具页也建议在 NPM 层加一层访问控制。打开Access Lists → Add Access List创建一个访问列表例如Name: demo-basic-auth Satisfy Any: Off在 Authorization 页添加账号Username: demo_user Password: 换成你自己的强密码在 Access 页可以继续加 IP 规则。如果只允许某个固定公网 IP 访问可以添加Allow: 203.0.113.10/32 Deny: all如果你没有固定公网 IP就先只使用 Basic Auth。不要为了省事把管理后台、数据库、NAS 后台直接暴露出去。然后回到刚才的 Proxy HostHosts → Proxy Hosts → Edit → Access List选择demo-basic-auth保存后再次访问curl -i -H Host: whoami.local.test http://127.0.0.1:8080/会看到401 Unauthorized说明 Basic Auth 生效。带上账号密码测试curl -u demo_user:你的强密码 -H Host: whoami.local.test http://127.0.0.1:8080/能返回 whoami 内容就说明反代和 Basic Auth 都正常。第七步配置 SSL 的正确姿势NPM 的 SSL 配置在 Proxy Host 的SSL页。如果你有自己的域名并且域名已经解析到这个 NPM 入口可以选择SSL Certificate: Request a new SSL Certificate Force SSL: On HTTP/2 Support: On HSTS Enabled: On再勾选同意 Lets Encrypt 服务条款填写邮箱保存即可。如果你只是用本文的whoami.local.test做本地测试不申请 Lets Encrypt 证书。这个域名只用于本地 Host 头测试不是公网可验证域名。如果你使用 cpolar 的 HTTPS 公网地址访问浏览器到 cpolar 公网入口这一段已经是 HTTPScpolar 到本机 NPM 这一段转发到8080的 HTTP 入口。临时演示、Webhook 调试、短时间外部访问可以这样做。长期公开服务建议使用自己的域名、固定地址和完整 HTTPS 配置。第八步用 cpolar 临时映射 NPM 演示入口现在只映射 NPM 的代理入口8080不映射81管理后台。启动临时 HTTP 隧道cpolar http 8080终端会输出公网访问地址格式类似https://xxxx.cpolar.top复制其中的域名部分例如xxxx.cpolar.top回到 NPM 后台编辑刚才的 Proxy Host在Domain Names里添加这个 cpolar 域名whoami.local.test xxxx.cpolar.top保存后在外部浏览器打开https://xxxx.cpolar.top如果前面配置了 Basic Auth浏览器会先弹出账号密码框。输入demo_user和你的强密码后页面会显示 whoami 返回的信息。这就是本文推荐的 cpolar 使用方式只暴露必要的业务入口或演示入口。NPM 管理后台不公开NAS 后台不公开数据库不公开Docker 管理端不公开。临时访问结束后在运行 cpolar 的终端按CtrlC隧道关闭后公网地址不再转发到你的本机服务。为什么不直接暴露每个服务端口假设你家里有这些服务服务内部端口风险NAS 后台5000/5001管理权限过高不应直接公网暴露Docker 面板9000能操作容器和挂载目录Grafana3000监控数据和内部地址不适合直接对公网展示Home Assistant8123涉及家居设备控制数据库3306/5432不应作为 Web 服务暴露临时演示页80/8080可以单独加认证后短期开放把每个端口都暴露出去等于把攻击面拆成很多入口。入口越多越难统一审计也越容易忘记关闭。NPM 的价值是把入口收拢外部只看到你允许的域名和路径每个入口都能单独加 Basic Auth 或访问列表后端服务可以继续留在 Docker 内部网络日志集中在 NPM 和具体服务里临时外网访问只需要开一个 cpolar 隧道。安全边界清单部署完成后按这张清单检查NPM 默认账号adminexample.com / changeme已经改掉管理员密码使用强密码且没有复用其他平台密码NPM 管理后台81端口只绑定127.0.0.1cpolar 只映射8080代理入口不映射81管理后台NAS 后台、数据库、Docker 面板、路由器后台不直接暴露到公网对外入口配置了 Basic Auth 或 Access ListWebSocket 类服务已打开Websockets Support需要长期公开的服务使用自己的域名和 HTTPS临时演示结束后关闭 cpolar 隧道定期查看 NPM 日志和后端服务日志不把真实密码、Token、数据库连接串写在公开页面里。再强调一次NPM 管理后台不要长期公开。你真正需要暴露的是被代理后的业务入口而且应该是必要、低权限、可关闭、可审计的入口。常见问题排查1. 容器启动失败先看状态cd /opt/npm-gateway sudo docker compose ps再看日志sudo docker compose logs --tail120 npm sudo docker compose logs --tail120 npm-db sudo docker compose logs --tail120 whoami如果数据库密码改过确认npm-db里的MYSQL_PASSWORD和npm里的DB_MYSQL_PASSWORD完全一致。如果数据目录权限异常执行cd /opt/npm-gateway sudo chown -R 1000:1000 data letsencrypt mysql sudo docker compose restart2. 端口冲突如果8080或8443已被占用启动时会报 bind 失败。查看占用sudo ss -lntp | grep -E :8080|:8443|:81macOS 上可以用sudo lsof -iTCP:8080 -sTCP:LISTEN sudo lsof -iTCP:8443 -sTCP:LISTEN sudo lsof -iTCP:81 -sTCP:LISTEN把 Compose 里的端口改成空闲端口例如ports: - 18080:80 - 18443:443 - 127.0.0.1:81:81然后重启cd /opt/npm-gateway sudo docker compose up -d后续访问和 cpolar 命令也要改成新端口cpolar http 180803. 反代返回 502502 通常表示 NPM 找不到后端服务按顺序查sudo docker inspect npm --format {{json .NetworkSettings.Networks}} sudo docker inspect whoami-demo --format {{json .NetworkSettings.Networks}}确认两个容器都在npm-net网络。再从同一个 Docker 网络里访问后端sudo docker run --rm --network npm-net curlimages/curl:8.8.0 -sS http://whoami-demo:80/能返回 whoami 内容说明后端可达。然后检查 Proxy HostScheme: http Forward Hostname / IP: whoami-demo Forward Port: 80不要把Forward Hostname / IP写成127.0.0.1。在 NPM 容器里127.0.0.1指的是 NPM 容器自己不是宿主机也不是 whoami 容器。4. WebSocket 异常表现通常是页面能打开但终端、实时日志、监控刷新、通知连接不正常。在对应 Proxy Host 里打开Websockets Support: On保存后重新访问。如果后端应用还有自己的 WebSocket 路径或反代说明按应用文档补充对应的自定义 Nginx 配置。5. SSL 证书申请失败Lets Encrypt 证书申请失败时先检查三点Domain Names填的是公网真实域名不是whoami.local.test域名解析到了你的公网入口证书验证请求可以到达 NPM 的 HTTP 入口。查看 NPM 日志sudo docker logs --tail120 npm如果只是临时使用 cpolar 的 HTTPS 地址不在 NPM 里给xxxx.cpolar.top申请证书直接使用 cpolar 提供的 HTTPS 公网地址访问。6. cpolar 地址打不开先确认本地 NPM 入口正常curl -I http://127.0.0.1:8080/再确认 cpolar 映射的是代理入口端口cpolar http 8080不要写成cpolar http 8181是 NPM 管理后台不应该公开。如果浏览器打开 cpolar 地址后显示 NPM 的默认页面或 404说明 Host 没匹配到 Proxy Host。把 cpolar 域名添加到对应 Proxy Host 的Domain Names里再保存。7. 源站地址填错这是家庭 Docker 反代里最常见的问题。如果后端服务和 NPM 在同一个 Docker 网络优先填容器名Forward Hostname / IP: whoami-demo Forward Port: 80如果后端服务跑在局域网其他机器上填那台机器的局域网 IPForward Hostname / IP: 192.168.1.50 Forward Port: 3000如果后端服务跑在宿主机本机但不在 Docker 网络里Linux Docker 环境可以先给 NPM 增加 host gatewayextra_hosts: - host.docker.internal:host-gateway然后在 NPM 里填Forward Hostname / IP: host.docker.internal Forward Port: 3000改完 Compose 后重启cd /opt/npm-gateway sudo docker compose up -d收个尾网关不是为了炫技是为了少暴露家庭服务器越折腾越需要一个清晰的边界。NPM 负责把多个服务入口收拢起来哪个能访问、转发到哪里、要不要 Basic Auth、要不要 WebSocket、要不要 HTTPS都放在同一个地方管理。cpolar 负责在需要外部临时访问时给必要入口开一条临时通道。关键不是“能不能从外网打开”而是“只打开该打开的那一个”。如果你照着做建议先用whoami跑通反代链路再接入真正的家庭服务。遇到问题也更好定位是 Docker、Proxy Host、502、SSL、WebSocket、外网访问还是安全边界设计出了问题。你现在卡在哪一步可以在评论区留一句Docker、Proxy Host、502、SSL、WebSocket、外网访问还是安全边界。我会按你的具体报错继续拆。