通过 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 地址与端口。
TIP推荐为云服务器开启防火墙,仅放行 80、81、443、7000 端口;其他由 FRP 动态创建的代理端口不需要对公网开放。
部署步骤
第一步:准备服务器环境
在云服务器上创建一个项目目录,用于存放所有配置文件。
# 创建项目目录并进入mkdir my-proxy-stackcd my-proxy-stack在该目录中,我们将创建两个核心文件:frps.toml 和 docker-compose.yml。
第二步:配置 frps 服务(frps.toml)
创建 frps.toml 文件,这是 FRP 服务端的配置文件。
nano frps.toml将以下内容粘贴进去,并根据注释进行修改:
# 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"关键点:
bindPort = 7000:FRP 的“握手”端口,需要暴露给公网,让内网的frpc能连接上来。token:务必设置一个长且复杂的字符串,防止未经授权的客户端连接。proxyBindAddr = 0.0.0.0:确保 NPM 容器可以访问到 frps 的代理端口。
TIP如果云服务器开启了防火墙/安全组,记得放行 TCP
7000端口。
第三步:编排 Docker 服务(docker-compose.yml)
创建 docker-compose.yml 文件,这是定义和运行我们所有服务的蓝图。
nano 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 动态创建的代理端口不对公网暴露。networks: - proxy-net:确保npm与frps在同一 Docker 网络中,可通过容器名(frps)直接通信。
第四步:启动云端服务
在 my-proxy-stack 目录下,运行以下命令来启动所有服务:
docker-compose up -d检查服务是否正常运行:
docker-compose ps您应该能看到 nginx-proxy-manager 和 frps 两个容器都处于 Up 状态。
第五步:配置 Nginx Proxy Manager
- 登录后台:在浏览器中访问
http://<你的服务器IP>:81。 - 首次登录:
- 默认邮箱:
admin@example.com - 默认密码:
changeme - 登录后会立即要求修改邮箱和密码,请务必修改为安全的凭据。
- 默认邮箱:
- 添加代理主机 (Proxy Host):
- 点击
Hosts→Proxy Hosts→Add Proxy Host。 - Details 标签页:
- Domain Names: 输入域名,例如
nas.yourdomain.com。 - Scheme: 选择
http。 - Forward Hostname / IP: 输入
frps(frps 容器在 Docker 网络中的名字)。 - Forward Port: 输入
8096(计划由 frpc 穿透过来的端口)。 - 勾选
Block Common Exploits。
- Domain Names: 输入域名,例如
- SSL 标签页:
- SSL Certificate: 选择
Request a new SSL Certificate。 - 勾选
Force SSL与HTTP/2 Support。 - 同意 Let’s Encrypt 服务条款。
- SSL Certificate: 选择
- 点击
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 安全访问。
TIP若证书申请失败,确认域名解析是否正确,80/443 端口是否被占用/屏蔽,以及服务器时间是否同步。