用 frp 实现内网 SSH 穿透:从搭建到排障

文章目录

家里的电脑、公司的开发机,或者实验环境中的虚拟机,经常没有固定公网 IP。人已经离开现场,但又想临时 SSH 登录回去,这时就需要把内网服务安全地“映射”到一个有公网 IP 的服务器上。frp 正是用于解决这类问题的反向代理工具。

本文将基于 Linux 和 systemd,使用 frp 将内网机器的 SSH 端口映射到云服务器,并给出验证、排障和基本的安全建议。

先理解 frp 的工作方式

frp 采用客户端与服务端配合的方式工作:

  • frps:运行在具有公网 IP 的服务器上,等待客户端连接并对外提供映射端口。
  • frpc:运行在内网机器上,主动连接 frps,把内网服务转发到公网服务器。

以 SSH 穿透为例,整体链路如下:

flowchart LR
    admin[远程管理电脑] -->|TCP 7001| frps[云服务器 frps]
    frpc[内网机器 frpc] -->|控制连接 7000| frps
    frpc -->|127.0.0.1:22| ssh[内网 SSH 服务]

本文约定两个端口:

端口用途暴露位置
7000frpc 连接 frps 的控制端口云服务器公网
7001映射到内网 SSH 的远程端口云服务器公网

需要强调一点:frp 不能凭空让内网机器获得公网入口,仍然需要一台能被公网访问的服务器。

准备环境

开始前确认以下条件:

  1. 云服务器具有公网 IP,并能修改安全组规则。
  2. 内网机器可以主动访问公网。
  3. 内网机器已经启用 SSH 服务,默认端口为 22。
  4. 两端最好使用相同版本的 frp,避免配置格式或协议不兼容。

frp 的发布包可以在 GitHub Releases 下载。下载时根据 CPU 架构选择文件,例如常见的 64 位 x86 服务器通常选择 linux_amd64。

解压后可以看到客户端和服务端分别需要的文件:

frpc
frpc.toml
frps
frps.toml

服务端只需要 frps 和 frps.toml,客户端只需要 frpc 和 frpc.toml。

tar -xzf frp_*_linux_amd64.tar.gz
cd frp_*_linux_amd64

下面的配置会把程序安装到 /usr/local/bin,并把配置放到 /etc/frp。这样程序和配置的路径固定,后续交给 systemd 管理也更方便。

配置服务端 frps

先在云服务器上放置程序和配置:

sudo install -Dm0755 frps /usr/local/bin/frps
sudo install -Dm0600 frps.toml /etc/frp/frps.toml

生成一个足够随机的鉴权 token:

openssl rand -hex 32

然后编辑 /etc/frp/frps.toml:

bindPort = 7000

auth.method = "token"
auth.token = "替换为刚刚生成的随机token"

bindPort = 7000 是 frp 的控制端口。auth.token 用于确认连接的客户端是否可信,服务端和客户端必须完全一致。

编辑配置文件时要保证文件可读,否则以普通方式启动服务时可能读取失败:

sudo chmod 600 /etc/frp/frps.toml

用 systemd 托管 frps

创建 /etc/systemd/system/frps.service:

[Unit]
Description=frp server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

启动服务并设置开机自启:

sudo systemctl daemon-reload
sudo systemctl enable --now frps

查看运行状态和日志:

systemctl status frps --no-pager
journalctl -u frps -n 100 --no-pager

配置文件修改后,可以先用 frp 自带命令检查语法,再重启服务:

sudo frps verify -c /etc/frp/frps.toml
sudo systemctl restart frps

配置客户端 frpc

在内网机器上安装客户端:

sudo install -Dm0755 frpc /usr/local/bin/frpc
sudo install -Dm0600 frpc.toml /etc/frp/frpc.toml

编辑 /etc/frp/frpc.toml:

serverAddr = "你的云服务器公网IP"
serverPort = 7000

auth.method = "token"
auth.token = "与服务端完全一致的token"

[[proxies]]
name = "home-ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 7001

各项配置的含义如下:

配置项说明
serverAddr云服务器的公网 IP 或域名
serverPort服务端监听的 frp 控制端口
localIP内网 SSH 服务所在地址,和 frpc 同机时可使用 127.0.0.1
localPort内网 SSH 实际监听的端口
remotePort外部访问云服务器时使用的端口

这里的 remotePort = 7001 是举例。它不能与 serverPort 或其他代理的远程端口重复,并且必须与服务端安全组中放行的端口一致。

创建客户端服务 /etc/systemd/system/frpc.service:

[Unit]
Description=frp client
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/frpc -c /etc/frp/frpc.toml
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

同样地,启动并检查客户端:

sudo systemctl daemon-reload
sudo systemctl enable --now frpc
systemctl status frpc --no-pager
journalctl -u frpc -n 100 --no-pager

放行云服务器端口

以阿里云为例,需要进入实例的安全组,添加入方向规则:

  • 协议类型:TCP。
  • 端口范围:至少包含 7000 和 7001。
  • 授权对象:测试阶段可暂时使用 0.0.0.0/0,生产环境应尽量限制为实际访问来源的 IP。

如果服务器本机启用了防火墙,还需要同步放行这些端口。不同发行版使用的防火墙工具不同,可以先确认防火墙状态,再按照实际环境添加规则。

端口放行后在云服务器上检查监听情况:

sudo ss -lntp | grep -E ':(7000|7001)\b'

正常情况下,应该能看到 frps 监听 7000,并在客户端连接成功后监听 7001。

登录内网 SSH

frp 连接建立后,在任意外部电脑上执行:

ssh -p 7001 内网机器的用户名@云服务器公网IP

这里容易混淆的是两个身份:

  • 内网机器的用户名 是 SSH 登录时使用的 Linux 用户。
  • 云服务器公网IP 是连接地址,因为 frp 把入口暴露在这台服务器上。

SSH 的端口参数是小写 -p,与 scp 使用的 -P 不同。原文档中的写法 ssh -o Port=7001 ... 同样可以工作,但直接使用 -p 更直观。

如果经常登录,可以把它写入 ~/.ssh/config:

Host home-pc
    HostName 203.0.113.10
    Port 7001
    User zhangsan
    ServerAliveInterval 30

之后直接执行 ssh home-pc 即可。把示例中的 IP 和用户名替换成自己的值。

常见问题排查

连接被拒绝或一直超时

依次检查控制端口、安全组和监听状态:

  1. frps 是否正在运行。
  2. 云服务器安全组是否放行 7000 和 7001。
  3. 客户端配置中的 serverAddr 是否正确。
  4. 云服务器上是否存在 7001 的监听记录。
systemctl status frps --no-pager
sudo ss -lntp | grep -E ':(7000|7001)\b'

客户端提示鉴权失败

检查两端 auth.method 和 auth.token 是否完全一致,包括大小写与多余空格。token 应当保存在受限权限的配置文件中,不要提交到 Git 仓库,也不要直接展示在截图里。

提示远程端口已被占用

remotePort 已经被另一个代理或系统进程占用。换一个未使用的端口,同时更新云服务器安全组规则和客户端配置。

服务启动后立即退出

优先查看日志定位配置错误或路径问题:

journalctl -u frps -n 100 --no-pager
journalctl -u frpc -n 100 --no-pager

如果 systemd 单元中使用了自定义路径,确认路径与实际安装位置一致。移动二进制文件或配置文件后,需要执行 systemctl daemon-reload。

安全建议

frp 解决的是网络可达性,不会代替应用本身的安全措施。尤其是 SSH 映射,建议至少做到以下几点:

  1. 使用 openssl rand -hex 32 之类的命令生成高强度 token,服务端和客户端共享同一份。
  2. 安全组只放行业务真正需要的端口。7001 是管理入口,条件允许时应限制访问来源 IP。
  3. 内网 SSH 使用密钥登录,并在确认密钥可用后关闭密码登录。
  4. 不要把数据库、Redis 管理端口等未经认证的服务直接映射到公网。
  5. 关注 frp 的版本更新,及时升级客户端和服务端。

完成以上配置后,云服务器负责提供公网入口,内网机器主动维持 frp 连接,外部电脑就能通过一个固定端口访问内网 SSH。整个方案的关键只有三点:两端 token 一致、控制端口可达、远程端口放行。遇到问题时沿着这三条链路检查,通常就能快速定位原因。