通过 Docker、Nginx Proxy Manager 与 FRP 安全地暴露内网服务
概述
本指南通过组合使用 Docker、Nginx Proxy Manager (NPM) 和 FRP,安全地将位于内网(如家庭 NAS、开发电脑)的服务发布到公网。目标效果:
- 只能通过自定义的 HTTPS 域名(如
nas.yourdomain.com)访问内网服务。 - 内网服务的真实 IP 和端口不暴露,所有流量经由加密隧道和反向代理,降低端口扫描风险。
- 使用 Docker Compose 统一管理云端服务,通过 NPM 的 Web UI 轻松管理域名和 SSL 证书。
架构概览
用户 → HTTPS 域名 → 云服务器(443) → NPM 容器 → Docker 内部网络 → frps 容器(代理端口) → FRP 隧道 → 内网 frpc 客户端 → 内网目标服务先决条件
- 一台云服务器 (VPS),拥有一个公网 IP 地址。
- 一个域名,并将需要使用的子域名(如
nas)通过 A 记录解析到云服务器公网 IP。 - 云服务器已安装 Docker 与 Docker Compose。
- 一台内网设备(如 NAS),并已知其内网 IP 地址与端口。
防火墙推荐为云服务器开启防火墙,仅放行
80、81、443、7000端口;其他由 FRP 动态创建的代理端口不需要对公网开放。
部署步骤
第一步:准备服务器环境
在云服务器上创建一个项目目录,用于存放所有配置文件。
mkdir my-proxy-stackcd my-proxy-stack在该目录中,我们将创建两个核心文件:frps.toml 和 docker-compose.yml。
第二步:配置 frps 服务(frps.toml)
创建 frps.toml 文件,这是 FRP 服务端的配置文件。
# frps 监听的地址,"0.0.0.0" 表示监听所有网络接口bindAddr = "0.0.0.0"# frps 用于和 frpc 客户端通信的主端口bindPort = 7000# [关键配置] 代理服务监听的地址# 设置为 0.0.0.0 意味着它会监听在容器的内部IP上,# 这样同一个 Docker 网络中的其他容器(如NPM)才能访问它。# 因为该端口不会通过 Docker 映射到宿主机,所以公网无法访问,是安全的。proxyBindAddr = "0.0.0.0"
# 认证配置[auth]# 指定认证方式与令牌method = "token"token = "YOUR_VERY_STRONG_AND_SECRET_TOKEN"WARNING
token务必设置一个长且复杂的字符串,防止未经授权的客户端连接。建议使用openssl rand -hex 32生成。
关键点:
bindPort = 7000:FRP 的”握手”端口,需要暴露给公网,让内网的frpc能连接上来。proxyBindAddr = 0.0.0.0:确保 NPM 容器可以访问到 frps 的代理端口。
TIP如果云服务器开启了防火墙/安全组,记得放行 TCP
7000端口。
第三步:编排 Docker 服务(docker-compose.yml)
创建 docker-compose.yml 文件,这是定义和运行我们所有服务的蓝图。
version: '3.8'
services: # 服务一:Nginx Proxy Manager (NPM) npm: image: 'jc21/nginx-proxy-manager:latest' container_name: nginx-proxy-manager restart: unless-stopped ports: # 公开的 Web 端口,用于接收外部用户的访问请求 - '80:80' # HTTP - '443:443' # HTTPS # NPM 自身的管理后台端口 - '81:81' volumes: - ./npm-data:/data - ./npm-letsencrypt:/etc/letsencrypt networks: # 将 NPM 加入到我们自定义的共享网络中 - proxy-net
# 服务二:FRP Server (frps) frps: image: 'snowdreamtech/frps:latest' container_name: frps restart: unless-stopped volumes: # 将宿主机的 frps.toml 配置文件挂载到容器内部 - ./frps.toml:/etc/frp/frps.toml ports: # [关键] 只发布 frps 的主服务端口 7000 到公网 - '7000:7000' # [注意] 千万不要在这里发布任何代理端口 networks: # 将 frps 也加入到同一个共享网络中,以便和 NPM 通信 - proxy-net
# 定义一个自定义的桥接网络,用于服务间的内部通信networks: proxy-net: driver: bridge端口安全
ports中只暴露80、81、443、7000四个端口。由 FRP 动态创建的代理端口不对公网暴露——它们仅存在于 Docker 内部网络proxy-net中,只有 NPM 可以访问。
第四步:启动云端服务
在 my-proxy-stack 目录下,运行以下命令来启动所有服务:
docker-compose up -d检查服务是否正常运行:
docker-compose ps您应该能看到 nginx-proxy-manager 和 frps 两个容器都处于 Up 状态。
第五步:配置 Nginx Proxy Manager
-
登录后台:在浏览器中访问
http://<你的服务器IP>:81。 -
首次登录:
配置项 默认值 邮箱 admin@example.com密码 changemeWARNING
登录后会立即要求修改邮箱和密码,请务必修改为安全的凭据。
-
添加代理主机 (Proxy Host):
点击
Hosts→Proxy Hosts→Add Proxy Host。Details 标签页:
配置项 值 Domain Names nas.yourdomain.comScheme httpForward Hostname / IP frps(Docker 内部容器名)Forward Port 8096(frpc 穿透的端口)Block Common Exploits 勾选 SSL 标签页:
配置项 值 SSL Certificate Request a new SSL CertificateForce SSL 勾选 HTTP/2 Support 勾选 点击
Save,NPM 会自动申请 SSL 证书并配置反向代理。
TIP可在 NPM 中为不同内网服务添加多个
Proxy Host,并配合Access List实现基于用户名/密码的二次保护。
第六步:配置内网客户端(frpc.toml)
在您的内网设备(如 NAS)上,创建 frpc.toml 配置文件。
# 服务器(云主机)的公网 IP 地址serverAddr = "YOUR_SERVER_PUBLIC_IP"# 与 frps.toml 中设置的 bindPort 一致serverPort = 7000
[auth]method = "token"token = "YOUR_VERY_STRONG_AND_SECRET_TOKEN"
# 定义一个代理[[proxies]]name = "nas_http_proxy" # 为代理起一个唯一的名字type = "tcp" # 代理类型localIP = "192.168.1.10" # 内网服务的 IPlocalPort = 5000 # 内网服务的端口remotePort = 5000 # 远程服务器的端口启动 frpc 客户端(以 Linux 为例):
./frpc -c ./frpc.toml建议将其设置为开机自启的服务。
第七步:最终验证
验证清单
- 成功验证:在浏览器中访问
https://nas.yourdomain.com,应能看到内网服务页面,且浏览器地址栏显示锁形图标。- 安全验证:尝试访问
http://<你的服务器IP>:8096,应当失败(无法连接)。这证明内网服务端口未暴露在公网,只能通过 NPM 安全访问。
NOTE若证书申请失败,检查以下几点:
- 域名 A 记录是否正确解析到服务器 IP
80/443端口是否被其他服务占用或被防火墙屏蔽- 服务器系统时间是否同步