服务端
用 frp + Nginx + Cloudflare 搭建内网穿透:把内网网站安全地暴露到公网
一、安装 frps
查最新版本(本文使用 0.69.1):https://github.com/fatedier/frp/releases
1 | # 下载并解压(按需替换版本号和架构,这里是 linux amd64) |
二、配置 frps:端口、鉴权、子域名
先生成一个随机鉴权 Token(这是客户端连接 frps 的凭证,务必保密):
1 | TOKEN=$(head -c 24 /dev/urandom | base64 | tr -d '/+=' | head -c 32) |
写入 /etc/frp/frps.toml:
1 | # frpc 客户端连接端口(直连 IP:7000,不走 Cloudflare) |
三个端口分别是什么
| 端口 | 作用 | 是否对公网开放 |
|---|---|---|
7000 |
frpc 客户端连接用,frpc 直连 YOUR_SERVER_IP:7000 |
是(ufw 放行) |
7080 |
HTTP vhost 入口,Nginx 从本机转发过来 | 否(仅 127.0.0.1,不放行) |
7500 |
管理面板(可选) | 本文不启用 |
为什么 7000 不能套 Cloudflare 小黄云?
Cloudflare 只代理 HTTP/HTTPS 的特定端口(80/443/8080/8443 等),7000不在内;而且 frp 跑的是自有二进制协议,不是 HTTP,Cloudflare 根本承载不了。所以 frpc 必须直连源站
**** IP:7000——这是 frp 架构的前提。客户端配置里写 IP 是私有的,不等于把 IP 放进公共 DNS 泄露(后者才是要避免的)。
三、systemd 服务 + 开机自启 + 防火墙
创建 /etc/systemd/system/frps.service:
1 | [Unit] |
启用并放行端口:
1 | systemctl daemon-reload |
验证:
1 | systemctl is-active frps # active |
四、配置 Nginx:把 443 流量反代到 frps vhost
frps 的 vhost 端口(7080)跑的是明文 HTTP,我们用 Nginx 在前面终结 TLS,再把流量(保留 Host 头)转给 frps。frps 靠 Host 头来路由到对应子域名的客户端。
在 Nginx 配置里(如 /etc/nginx/conf.d/your-site.conf)加一个通配 server 块:
1 | # WebSocket 支持(map 段放 http 块顶部,全局一次即可) |
通配块 vs 精确块:Nginx 的
server_name匹配,精确名称优先于通配符。所以你已有的精确子域名站点(如blog.example.com
走自己的后端)不受影响;只有没单独定义的子域名才会落进这个通配块 → frps。这就是”新增穿透站不用动 Nginx”的关键:一个通配块把所有
*.example.com都接管了。
生效:
1 | nginx -t && systemctl reload nginx |
此时本地测试链路(还没客户端,frps 会返回 404,属正常,证明管道通了):
1 | curl -H "Host: app1.example.com" http://127.0.0.1:7080/ # 应返回 frp 的 404 |
五、配置 Cloudflare 子域名(隐藏源站 IP)
frp 的 subdomainHost 让客户端用 subdomain = "app1" 自动拼成 app1.example.com,但 DNS 还是要一条一条加。
正确做法:每个子域名加一条「橙云」A 记录
在 Cloudflare → DNS 里,为每个要用作穿透的子域名加:
| 类型 | 名称 | 内容 | 代理状态 |
|---|---|---|---|
| A | app1 |
YOUR_SERVER_IP |
已代理(橙云) |
| A | app2 |
YOUR_SERVER_IP |
已代理(橙云) |
橙云 = Cloudflare 代理:对外只显示 Cloudflare 的 IP,源站真实 IP 被隐藏,还白嫖了 TLS 和 DDoS 防护。
千万别加「灰云通配记录」
新手很容易想偷懒,加一条灰云(DNS only)的 *.example.com → YOUR_SERVER_IP。Cloudflare 免费套餐无法代理通配记录,它只能是灰云——于是任何人 dig anything.example.com
**** 都能直接拿到你的源站真实 IP,Cloudflare 的保护形同虚设。一旦源站 IP 暴露,攻击者可以绕过 Cloudflare 直接打你源站,殃及你所有服务。
简单记:橙云逐条加 = 隐藏 IP;灰云通配 = 暴露 IP。多花几秒钟点几下,值得。
六、客户端 frpc 配置
在内网机器上(你的电脑、家里 NAS 等),下载同版本的 frpc,写入 frpc.toml:
1 | serverAddr = "YOUR_SERVER_IP" # 直连 IP,7000 不走 Cloudflare |
启动:
1 | ./frpc -c frpc.toml |
访问 https://app1.example.com,就是你的内网网站了。
⚠️ 一个真实踩坑:macOS / IPv6 的 localhost
如果在 macOS 上跑 frpc,本地服务用 localhost 能访问,但 frp 报 “The page … currently unavailable. The server is powered by frp.”,服务端日志出现 do http proxy request error: EOF——
原因:macOS 的 localhost 优先解析到 IPv6 ::1,很多开发服务器只绑 ::1、不绑 IPv4 127.0.0.1。而 frpc 配的是 localIP = "127.0.0.1"(IPv4),连不上。
解决:把 frpc 的 localIP 改成 ::1:
1 | localIP = "::1" # 本地服务监听 IPv6 时用这个;监听 IPv4 才用 127.0.0.1 |
验证方法:
curl http://127.0.0.1:端口失败但curl http://localhost:端口正常 = 服务只在 IPv6 上。frpc 里写::1即可。
七、新增一个穿透站(服务端零改动)
这是这套架构最爽的地方。要再加一个站 app2.example.com:
- Cloudflare:加一条橙云 A 记录
app2 → YOUR_SERVER_IP。 - 客户端 frpc.toml 加一段:
1 | [[proxies]] |
- 客户端重启 frpc。
服务端(frp 配置、Nginx、防火墙、证书)一概不动。 因为 subdomainHost + Nginx 通配块已经就绪。
八、安全与运维提示
- Token 是命门:泄露 = 任何人都能挂代理白嫖你的服务器做中转。务必保密,定期换。
- 面板能不开就不开:frps 的 web 面板(
webServer.port = 7500)默认只读监控,没强需求就别开,少一个暴露面。 - 想加密 frpc**↔**frps 隧道:服务端
transport.tls.force = true,客户端transport.tls.enable = true,防止 token 和流量被嗅探。 - SSH/RDP 这类不想暴露公网端口的:用
type = "stcp"(密钥点对点),服务端一个额外端口都不用开,比 TCP 转发安全得多。 - 日常命令:
1 | systemctl status frps # 状态 |
小结
整套方案的核心是三层分工:
| 层 | 组件 | 职责 |
|---|---|---|
| 入口/安全 | Cloudflare 橙云 | 隐藏 IP、TLS、防 DDoS |
| TLS/路由 | Nginx 通配块 | 终结 TLS,按域名转发 |
| 穿透 | frps + subdomainHost | 动态路由到各客户端 |
配置好之后,日常加站就是「DNS 加一条 + 客户端加一段」两步,服务端永远是透明的。希望对你有帮助。