1. 项目概述:为什么我们需要更安全的frp?
如果你正在用frp做内网穿透,大概率已经体会过它的便捷——把家里的NAS、开发中的Web服务,或者办公室的监控摄像头暴露到公网,只需要一个轻量级的客户端和一个有公网IP的服务器。但便捷的另一面,是安全风险。默认配置下,frp客户端与服务端之间的通信是明文的,认证也仅靠一个简单的Token。这意味着,一旦你的服务器IP暴露,传输的数据就可能被监听,甚至有人可以伪造客户端连接到你的内网。
我见过太多因为配置疏忽导致的安全事件。比如,有人把Token直接写在GitHub的公开配置文件里;或者服务器7000端口对外开放,却没有任何加密措施,流量在公网上“裸奔”。frp v0.52.3版本在安全方面做了不少强化,尤其是TLS加密默认开启,这给我们提供了一个构建更健壮穿透隧道的机会。今天,我就结合自己踩过的坑和最佳实践,带你从零开始,搭建一套同时启用TLS双向加密和强Token验证的frp环境。这不仅仅是打开两个开关,而是理解每个参数背后的安全逻辑,打造一个既可用又让人放心的内网穿透方案。
2. 核心安全机制深度解析
在动手改配置文件之前,我们必须搞清楚两件事:TLS加密到底保护了什么,以及Token验证如何阻止非法接入。很多人配置了却不知其所以然,出了问题只会干瞪眼。
2.1 TLS加密:从“可选”到“默认”的通信护甲
从frp v0.50.0版本开始,transport.tls.enable的默认值从false改为了true。这是一个非常重要的安全转向。它意味着什么?简单说,以前客户端(frpc)和服务端(frps)之间的控制通道和数据通道,默认是明文传输的。现在,它们会尝试建立一个加密的TLS隧道。
这个TLS和你在Web服务器上配置的HTTPS证书(用于type = “https”的代理)是两码事。后者是保护最终用户浏览器到frps这段连接。而transport.tls保护的是frpc到frps这段最核心的、可能穿越整个互联网的连接。它确保了:
- 机密性:所有传输的数据,包括你的认证Token、内网服务的流量,都被加密,即使被截获也无法解密。
- 完整性:数据在传输过程中不被篡改,防止中间人攻击。
- 身份验证(可选):通过配置证书,可以验证服务端的身份(甚至客户端身份),确保你连接的是真正的、可信的frps服务器。
在v0.52.3的配置模板里,相关配置集中在transport.tls段:
transport.tls.enable = true # transport.tls.certFile = “client.crt” # transport.tls.keyFile = “client.key” # transport.tls.trustedCaFile = “ca.crt” # transport.tls.serverName = “example.com”默认只开启enable = true,使用的是frp内置的或系统信任的根证书来验证服务端证书(如果服务端提供了)。这是一种简便的“服务器验证”模式。更安全的做法是启用双向TLS(mTLS),即客户端和服务端互相验证证书,这需要我们自签名或使用受信任的CA签发证书,并配置certFile,keyFile和trustedCaFile。
注意:如果你遇到类似“创建 tls 客户端 凭据时发生严重错误。内部错误状态为 10013”的错误,这通常与Windows系统(尤其是Win7)的TLS协议支持有关,或者系统时间不正确。对于老旧系统,可能需要安装系统补丁(如Win7的TLS 1.2支持补丁)或检查系统时间是否与网络时间同步。
2.2 Token验证:守住入口的第一道门
Token是frp最基础的认证方式。在auth.method = “token”时,客户端必须提供与服务器端一致的auth.token字符串,才能建立连接。这听起来简单,但实际使用中有几个关键点:
- Token不是密码:它通常是一个长随机字符串,用于机器间认证,不应使用有意义的单词。
- 存储安全:Token应避免硬编码在配置文件中并上传至公开仓库。更安全的做法是使用
auth.tokenSource从环境变量或文件中读取。 - 作用域:
auth.additionalScopes参数可以细化Token的权限,例如限定其仅用于心跳保活(HeartBeats)或建立新工作连接(NewWorkConns),实现权限最小化原则。
在v0.52.3中,配置示例如下:
auth.method = “token” auth.token = “your_strong_token_here” # 或者从文件读取 # auth.tokenSource.type = “file” # auth.tokenSource.file.path = “/etc/frp/token”一个强Token应该像这样:a3F8z!pL9@qY2mN5cR$vU7w*Zb4E6gH,包含大小写字母、数字和特殊字符,长度建议在32位以上。
2.3 安全组合拳:TLS + Token 的实际效果
单独使用TLS或Token都有缺陷。仅用Token,通信内容可能被窃听;仅用TLS(且不验证客户端),如果证书配置不当,仍可能被中间人攻击。二者结合则形成了纵深防御:
- TLS加密通道首先建立,确保后续所有通信(包括Token的传输)都在加密隧道中进行。
- Token验证在加密通道内进行,作为业务层的准入许可。
这样,即使攻击者探测到你的frps端口,他也无法解密TLS握手包,更无法获取或伪造有效的Token。这套组合是目前兼顾安全与复杂度的最佳实践。
3. 实战配置:从零搭建安全frp环境
理论讲完,我们进入实战。我会以Linux服务器(frps)和Linux内网主机(frpc)为例,演示最安全的双向TLS + Token验证配置。Windows系统操作逻辑类似,主要是路径和启动方式的区别。
3.1 准备工作与材料清单
在开始前,你需要准备:
- 一台具有公网IP的服务器:用于部署frps(服务端)。假设公网IP为
123.123.123.123,系统为Ubuntu 22.04。 - 一台或多台内网机器:用于部署frpc(客户端)。
- frp v0.52.3 程序:从GitHub官方仓库(fatedier/frp)的Release页面下载对应系统架构的压缩包。
- 一个强密码生成器:用于生成Token。
首先,在服务器和客户端分别下载并解压frp:
wget https://github.com/fatedier/frp/releases/download/v0.52.3/frp_0.52.3_linux_amd64.tar.gz tar -zxvf frp_0.52.3_linux_amd64.tar.gz cd frp_0.52.3_linux_amd64目录里我们会用到frps、frps.toml(服务端)和frpc、frpc.toml(客户端)。
3.2 生成自签名TLS证书(双向验证)
为了实现最安全的双向TLS,我们需要创建自己的证书颁发机构(CA),并用它来签发服务器和客户端证书。这里使用openssl命令。
第一步:生成根CA证书(在任意一台机器操作,完成后分发)
# 生成CA私钥 openssl genrsa -out ca.key 4096 # 生成CA自签名证书(有效期10年) openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj “/C=CN/ST=Beijing/L=Beijing/O=MyOrg/OU=IT/CN=MyRootCA”现在你得到了ca.key(私钥,务必保密)和ca.crt(根证书,需要分发给所有客户端和服务端)。
第二步:生成服务器端证书在服务器上操作:
# 生成服务器私钥 openssl genrsa -out server.key 4096 # 生成证书签名请求(CSR) openssl req -new -key server.key -out server.csr -subj “/C=CN/ST=Beijing/L=Beijing/O=MyOrg/OU=Server/CN=frps.mycompany.com” # 使用CA证书签发服务器证书 openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crtfrps.mycompany.com是证书的主题,最好设置为你的服务器域名或一个标识名。你会得到server.key,server.csr,server.crt。其中server.csr可丢弃,server.key和server.crt用于服务端配置。
第三步:生成客户端证书在每个客户端上操作,或在一处生成后安全分发:
# 生成客户端私钥 openssl genrsa -out client.key 4096 # 生成客户端CSR openssl req -new -key client.key -out client.csr -subj “/C=CN/ST=Beijing/L=Beijing/O=MyOrg/OU=Client/CN=client-01” # 使用CA证书签发客户端证书 openssl x509 -req -days 3650 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crtclient-01可以替换为每个客户端的唯一标识。得到client.key,client.csr,client.crt。
实操心得:将生成的
ca.crt、server.crt、server.key放到服务器frp目录下的./certs/子目录中。将ca.crt、client.crt、client.key放到每个客户端frp目录下的./certs/子目录中。这样便于管理,也符合最佳安全实践——密钥文件权限应设置为600 (chmod 600 *.key)。
3.3 配置服务端 (frps.toml)
编辑服务器上的frps.toml文件。关键配置如下:
# frps.toml bindAddr = “0.0.0.0” bindPort = 7000 # 1. 启用并配置TLS transport.tls.enable = true # 指定服务端自己的证书和私钥 transport.tls.certFile = “./certs/server.crt” transport.tls.keyFile = “./certs/server.key” # 指定信任的CA根证书,用于验证客户端证书 transport.tls.trustedCaFile = “./certs/ca.crt” # 强制要求客户端提供证书(双向TLS) transport.tls.forceTLS = true # 注意:这个参数名是示例,实际frp中是通过提供trustedCaFile并要求客户端证书来实现的。在frp中,服务端配置了trustedCaFile,且客户端提供了certFile,即构成双向验证。 # 2. 配置Token认证 auth.method = “token” # 生成一个强Token,例如:openssl rand -base64 32 auth.token = “a3F8z!pL9@qY2mN5cR$vU7w*Zb4E6gH” # 3. Web仪表板(可选,建议设置强密码并限制访问IP) webServer.addr = “127.0.0.1” # 强烈建议只监听本地,通过SSH隧道或反向代理访问 webServer.port = 7500 webServer.user = “admin” webServer.password = “AnotherStrongPassword!” # 4. 日志配置 log.to = “./frps.log” log.level = “info” log.maxDays = 7配置要点解析:
bindAddr = “0.0.0.0”表示监听所有网卡,确保能接收到公网请求。transport.tls.trustedCaFile指向我们自签的CA证书。服务端会用这个CA证书去验证客户端连接时提供的证书 (client.crt) 是否由该CA签发。这是实现双向TLS验证的关键。auth.token是共享密钥,务必复杂且唯一。- Web仪表板监听在
127.0.0.1是安全最佳实践,防止未授权的外部访问。你可以通过SSH端口转发 (ssh -L 7500:localhost:7500 user@server) 来安全访问。
3.4 配置客户端 (frpc.toml)
编辑内网机器上的frpc.toml文件:
# frpc.toml serverAddr = “123.123.123.123” # 你的公网服务器IP serverPort = 7000 # 1. TLS配置(必须与服务端匹配) transport.tls.enable = true # 指定客户端自己的证书和私钥 transport.tls.certFile = “./certs/client.crt” transport.tls.keyFile = “./certs/client.key” # 指定信任的CA根证书,用于验证服务器端证书 transport.tls.trustedCaFile = “./certs/ca.crt” # 指定服务端证书中期望的域名(如果服务器证书CN是域名,则填写) # transport.tls.serverName = “frps.mycompany.com” # 2. Token认证(必须与服务端一致) auth.method = “token” auth.token = “a3F8z!pL9@qY2mN5cR$vU7w*Zb4E6gH” # 3. 定义一个SSH穿透代理 [[proxies]] name = “home-ssh” type = “tcp” localIP = “127.0.0.1” localPort = 22 remotePort = 60022 # 在服务器上开放的这个端口,将转发到本地的22端口 # 可选:启用代理层加密和压缩 transport.useEncryption = true transport.useCompression = true # 4. 定义一个HTTP网站穿透代理 [[proxies]] name = “web-service” type = “http” localIP = “127.0.0.1” localPort = 8080 customDomains = [“app.mydomain.com”] # 需要将app.mydomain.com的DNS A记录指向你的服务器IP 123.123.123.123 # HTTP基础认证(增加一层保护) httpUser = “webadmin” httpPassword = “YourHTTPPass123”配置要点解析:
transport.tls.certFile和transport.tls.keyFile是客户端的身份凭证。transport.tls.trustedCaFile使得客户端能够验证服务器证书的真伪,防止连接到假冒的frps。transport.tls.serverName用于TLS握手时的SNI(服务器名称指示),如果服务器证书是针对特定域名签发的,这里填写该域名可以确保证书验证正确。- 在代理配置中,
transport.useEncryption = true会在TLS隧道的基础上,在frp应用层再进行一次加密,属于“双保险”,但会略微增加CPU开销。对于SSH这种本身已加密的流量,非必需;对于HTTP明文服务,建议开启。
3.5 启动与验证
启动服务端:
./frps -c ./frps.toml &检查日志tail -f frps.log,看到类似“frps started successfully”和“tls is enabled”的提示。
启动客户端:
./frpc -c ./frpc.toml &检查客户端日志,应看到“login to server success”和“proxy [home-ssh] start success”等信息。
验证连接:
- SSH连接测试:从外网任意机器,执行
ssh -p 60022 your_inner_username@123.123.123.123。如果配置正确且内网机器SSH服务正常,你应该能成功登录。 - TLS握手验证:可以使用
openssl s_client命令测试TLS连接:
观察输出,确认握手成功,并看到证书验证信息。openssl s_client -connect 123.123.123.123:7000 -servername frps.mycompany.com -CAfile ./certs/ca.crt -cert ./certs/client.crt -key ./certs/client.key - 仪表板查看:通过SSH隧道访问
http://localhost:7500,输入仪表板用户名密码,你应该能看到在线的客户端和活跃的代理。
4. 高级安全加固与性能调优
基础配置跑通后,我们可以进一步收紧安全策略并优化性能。
4.1 网络层加固:防火墙与端口策略
仅靠frp自身配置不够,系统防火墙是第一道屏障。
在服务端(使用ufw为例):
sudo ufw allow 7000/tcp # 只开放frps TLS端口 sudo ufw allow from 192.168.1.0/24 to any port 7500 # 只允许内网IP访问仪表板(如果你改了监听地址) sudo ufw enable理想情况下,frps的7000端口应只允许已知的客户端源IP访问。如果你的客户端IP固定,可以设置更精确的规则,如sudo ufw allow from 客户端公网IP to any port 7000 proto tcp。
在客户端: 通常无需额外开放入站端口,因为frpc是主动出站连接。但应确保本地防火墙允许frpc程序访问外网。
4.2 配置文件的权限与安全管理
配置文件包含Token和路径信息,必须严格限制访问权限。
# 在服务器和客户端上执行 chmod 600 frps.toml frpc.toml chmod 600 ./certs/*.key # 私钥文件权限必须为600永远不要将配置文件或证书文件提交到公开的版本控制系统(如GitHub)。可以使用.gitignore忽略它们,或使用配置管理工具的秘密管理功能。
4.3 使用Token文件替代明文Token
为了避免Token在配置文件中明文出现,可以使用auth.tokenSource从文件读取:
# frpc.toml 中替换 auth.token auth.method = “token” auth.tokenSource.type = “file” auth.tokenSource.file.path = “/etc/frp/.token”然后在/etc/frp/.token文件中写入Token字符串,并设置权限chmod 600 /etc/frp/.token。这样即使配置文件泄露,Token也不会直接暴露。
4.4 性能与稳定性调优参数
在transport部分,有几个参数对高并发或高延迟网络环境有帮助:
# 连接池大小,预建立连接以减少延迟,默认为0 transport.poolCount = 5 # TCP流多路复用,减少连接数,默认开启且建议保持 transport.tcpMux = true # 心跳间隔和超时,确保连接存活 transport.heartbeatInterval = 30 transport.heartbeatTimeout = 90 # 连接服务器超时时间 transport.dialServerTimeout = 10poolCount在客户端需要频繁建立多个代理连接时特别有用。tcpMux能显著提升性能,尤其是在有大量短连接代理时。
5. 故障排查与常见问题实录
即便配置再仔细,在实际部署中还是会遇到各种问题。这里记录几个我亲自踩过的坑和解决方法。
5.1 连接失败:TLS握手错误
问题现象:客户端日志报错“tls: failed to verify certificate: x509: certificate signed by unknown authority”或“tls: bad certificate”。
排查思路:
- 检查CA证书一致性:确保客户端和服务端使用的
ca.crt文件内容完全一致。用md5sum ca.crt在两边比对。 - 检查证书链:确保客户端配置的
certFile和keyFile是配对的,且由指定的trustedCaFile签发。可以用命令验证:openssl verify -CAfile ca.crt client.crt - 检查serverName:如果服务器证书的CN是域名(如
frps.mycompany.com),确保客户端配置了transport.tls.serverName = “frps.mycompany.com”,且连接时使用的serverAddr能解析到该域名,或者通过hosts文件指向正确IP。SNI不匹配会导致验证失败。 - 检查时间同步:TLS证书验证依赖于系统时间。如果客户端或服务器时间偏差过大(如超过证书有效期),握手会失败。使用
date命令检查,并通过NTP同步时间。
5.2 Token验证失败
问题现象:客户端日志报错“authorization failed”或“invalid token”。
排查思路:
- 逐字符核对Token:Token字符串必须完全一致,包括大小写和所有特殊字符。最稳妥的方法是使用
echo -n “token” | md5sum在两边计算哈希值进行比对,避免肉眼检查的疏漏。 - 检查空白字符:复制Token时,可能无意中带入空格或换行符。确保配置文件中的Token字符串没有多余的空格。使用
cat -A frpc.toml | grep token可以显示不可见字符。 - 确认认证方法:服务端和客户端的
auth.method必须同为“token”。
5.3 能连接但代理不通
问题现象:frpc显示“login to server success”,但通过公网IP:端口访问服务时超时或连接被拒绝。
排查思路:
- 检查服务端防火墙:确认服务器安全组或iptables/ufw规则允许了代理的
remotePort(如60022)。sudo ufw status numbered查看。 - 检查客户端本地服务:在客户端机器上,执行
curl http://127.0.0.1:8080或ssh localhost -p 22,确保本地服务本身是正常监听的。 - 检查代理类型匹配:确保代理
type正确。例如,本地是HTTP服务,代理类型就应该是“http”或“https”,而不是“tcp”。“tcp”是原始TCP转发,“http”代理会处理HTTP协议头,支持基于域名的路由。 - 检查域名解析:对于HTTP/HTTPS代理,如果使用了
customDomains,确保该域名已正确解析到frps服务器的公网IP。可以用nslookup app.mydomain.com或dig app.mydomain.com验证。
5.4 关于“创建 tls 客户端 凭据时发生严重错误。内部错误状态为 10013”
这是一个典型的Windows系统错误,尤其在Windows 7或Server 2008 R2上常见。
原因:系统缺少支持TLS 1.2的更新,或者系统策略禁用了相关的加密套件。
解决方案:
- 安装系统更新:确保系统已安装所有重要更新,特别是用于启用TLS 1.2的补丁(如KB3140245)。
- 修改注册表(高级):可以尝试启用更强的加密算法。操作注册表有风险,请先备份。打开注册表编辑器,定位到
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client,确保DisabledByDefault值为0,Enabled值为1。 - 降级frp TLS配置(临时方案):如果问题紧急且无法立即更新系统,可以尝试在客户端配置中暂时禁用TLS(
transport.tls.enable = false)作为测试,但这会牺牲安全性,仅用于定位问题,生产环境不推荐。 - 升级操作系统:长远来看,将客户端升级到Windows 10或更高版本是根本解决之道。
5.5 性能问题:高延迟或低吞吐量
问题现象:连接速度慢,传输大文件时速率不理想。
优化方向:
- 调整加密与压缩:对于已经通过TLS加密的流量,代理层的
useEncryption可能带来额外开销且收益有限,可以考虑关闭。对于文本类流量(如HTTP),开启useCompression有帮助;对于已压缩的图片、视频流量,关闭压缩反而能提升速度。 - 调整TCP参数:在服务端,可以尝试优化内核TCP参数,如增大TCP缓冲区大小。编辑
/etc/sysctl.conf,添加:
执行net.core.rmem_max = 67108864 net.core.wmem_max = 67108864 net.ipv4.tcp_rmem = 4096 87380 67108864 net.ipv4.tcp_wmem = 4096 65536 67108864sysctl -p生效。 - 检查网络路径:使用
mtr或traceroute检查客户端到服务器之间的网络质量,是否存在高延迟或丢包节点。有时更换服务器机房或网络运营商能有改善。 - 使用更高效的传输协议:在
transport.protocol中尝试“kcp”或“quic”(如果网络丢包严重)。KCP能以牺牲一定带宽为代价换取更低的延迟和更好的抗丢包性。配置示例:transport.protocol = “kcp” # 可调整KCP参数 # transport.kcp.mtu = 1350 # transport.kcp.sndwnd = 1024 # transport.kcp.rcvwnd = 1024 # transport.kcp.nodelay = 1 # transport.kcp.interval = 20 # transport.kcp.resend = 2 # transport.kcp.nc = 1
配置安全且高效的frp服务是一个持续调优的过程。核心在于理解TLS和Token各自守护的边界,并根据你的实际网络环境和安全要求,在配置文件中找到那个平衡点。从默认的明文通信到强制的TLS加密,frp正在引导用户走向更安全的实践,而我们能做的,就是跟上这个步伐,把每一个细节落到实处。