办公室角落那台旧笔记本上跑着一个库存查询页面,仓库的同事在外地出差,打开链接就是一片空白——这是我被问得最多的一类问题。内网穿透这套东西听起来像是网络工程师的活,实际上只要你手上有台能被公网访问的服务器,再加上 nps 这个工具,半小时就能把内网里的服务安全地引出去。nps 的做法和路由器上的端口映射完全不同:它不需要你有公网 IP,也不用去动路由器,而是让内网机器主动向公网服务器拨出一条长连接,外部请求顺着这条连接回到内网,再由内网机器交给本地服务。这篇文章我按自己的实际操作顺序来写,从选服务器、改 nps.conf、拉起 npc,到隧道类型的取舍和几个我踩过的坑,配置都给到可以直接抄的程度。适合手上有 NAS、开发机、树莓派或者办公内网服务,需要临时或长期对外开一个入口的人看;也适合想搞清楚这类工具底层到底在干什么的读者。
1. nps 到底替你做了哪件事:把内网地址翻译成公网入口
1.1 从一个仓库同事打不开的页面说起
内网服务不外乎长这样:某台机器上跑着192.168.1.20:8000,同一个路由器下的电脑都能打开,出了这个网段就彻底失联。想让外面的人访问,通常只有三条路可走。第一条是把服务搬到公有云上,改造成本高,数据还得跟着迁;第二条是在路由器上做端口映射,前提是运营商给了你公网 IP,很多家庭宽带和办公宽带早就换成大内网了,而且多个服务抢一个 80 端口也难协调;第三条就是本文要讲的内网穿透——内网机器主动向公网服务器拨号,维持一条常驻连接,公网服务器收到外部请求后,把字节流顺着这条已经建好的连接塞回内网。
nps 属于第三条路里比较完整的一套实现。它由两部分组成:跑在公网服务器上的服务端叫nps,跑在内网机器上的客户端叫npc。整个链路的走向是:外部用户访问公网IP:9001→ nps 发现 9001 这个端口绑定在某条隧道上 → 把请求写进对应 npc 的长连接 → npc 收到后转发给隧道里配置的local_ip:local_port→ 内网服务返回响应 → 原路回到外部用户。整个过程里,路由器、光猫、运营商那边一个字都不用改。
1.2 nps 和 frp、ngrok 这类方案的差别在哪
同一个需求,工具选型其实取决于你要解决的是"一次性把服务露出去"还是"长期管理一批内网节点"。我把自己用过的几类放一起对比一下,方便你对号入座。
| 方案类型 | 典型代表 | 配置方式 | 适合的场景 | 需要留意的点 |
|---|---|---|---|---|
| 面板驱动型 | nps | 服务端 Web 面板 + 客户端一个 vkey | 多台内网机器、需要统一查看在线状态 | 面板本身要加固,别裸奔在公网 |
| 配置驱动型 | frp | 服务端与客户端都写配置文件 | 配置纳入版本管理、批量下发 | 改一条隧道要动配置并重启客户端 |
| 托管服务型 | ngrok 类 | 注册账号、一条命令拉起 | 临时演示、给别人看几分钟 | 免费额度有限,域名通常是随机分配的 |
我自己的习惯是:给客户做临时演示用托管类,省事;公司内部要长期挂着几台机器,用 nps 的面板管着;如果整个流程要写进部署脚本、跟着代码仓库走,那配置驱动的方案更顺手。nps 最舒服的地方是客户端侧几乎零配置——内网机器只要一个vkey就能上线,隧道怎么开、开在哪个端口,全在服务端面板上点几下就完事,不用登录到内网机器上改文件。
1.3 两个角色、一个 vkey:先把概念对齐
开始动手前,有几个名词先说明白,不然后面看配置文件会晕。
- bridge_port(默认 8024):npc 拨号连接 nps 用的端口,也叫桥接端口。它只负责"把管道建起来",不承载业务流量。
- 服务端口:隧道对外暴露的端口,比如你在面板上建了 9001,那外部访问的就是
公网IP:9001。 - local_ip / local_port:内网服务实际监听的地址和端口,注意这两个值的参照物是npc 所在的那台机器,不是 nps 服务器。
- vkey:客户端身份凭据。nps 的配置文件里有个
public_vkey,等于"允许哪些客户端免账号接入"的默认口令。
提示:
vkey泄露的后果和把隧道入口的钥匙交出去是一样的。默认的123一定要换掉,而且安装完第一件事就是改它。
有一条经验值得提前说:很多新手会把"服务端口"和"bridge_port"混在一起,觉得既然客户端连的是 8024,那 8024 就该是业务入口。实际上这两条路是分开的,8024 只需要对客户端所在网络开放,业务端口才对最终用户开放。搞清楚这一点,后面排查问题时会少走很多弯路。
2. 公网那台机器:从开放端口到面板能登进去
2.1 端口规划先做,别等报错再回头改
服务端最容易翻车的地方不是软件本身,而是端口。我的做法是装之前先在纸上(或者备忘录里)列一张清单,照着这张表去开安全组和系统防火墙,一次到位。
| 用途 | 默认端口 | 需要放行给谁 | 说明 |
|---|---|---|---|
| 桥接端口 | 8024/tcp | 所有 npc 客户端 | 可改成别的端口,改完两端都要同步 |
| Web 管理面板 | 8080/tcp | 只放行你自己的办公 IP | 强烈建议不要对全网开放 |
| HTTP 转发 | 80/tcp | 所有访问者 | 做域名转发时才需要 |
| HTTPS 转发 | 443/tcp | 所有访问者 | 需要证书文件 |
| 业务隧道 | 9000-9100/tcp | 所有访问者 | 按需规划成一个区间,别用到哪开到哪 |
| P2P 牵线 | 6000/tcp+udp | 客户端与访问者 | 只有用点对点模式时才需要 |
这里有个反复出现的坑:云服务器的安全组和系统自带防火墙是两个独立层面,安全组放行了,系统里firewalld或ufw拦着,照样不通。我一般会把三个命令连着执行一遍确认:先看本机有没有监听(ss -tlnp | grep 8024),再从本机curl一下面板端口,最后换个网络环境用telnet 公网IP 8024验证外部可达性。三段都通,才说明这条链路是干净的。
2.2 安装与用 systemd 托管起来
我习惯把 nps 装在/opt/nps,目录结构里会有nps二进制、conf/、web/三部分。官方提供了一键安装服务的命令,装完可以直接用systemctl管:
mkdir -p /opt/nps && cd /opt/nps # 解压发行包后,目录里应包含 nps、conf、web ./nps install systemctl daemon-reload systemctl enable --now nps systemctl status nps --no-pager如果你不太喜欢install帮你写的东西,或者需要自定义运行参数,自己写一个 unit 文件更可控:
[Unit] Description=nps server After=network.target [Service] Type=simple WorkingDirectory=/opt/nps ExecStart=/opt/nps/nps Restart=always RestartSec=5 LimitNOFILE=65535 [Install] WantedBy=multi-user.target为什么坚持用 systemd 而不是nohup ./nps &?三个非常实在的理由:开机自启,服务器重启后不用你再登录一次;进程崩了自动拉起来,Restart=always那行就是干这个的;日志统一进journalctl,排查问题时journalctl -u nps -f就能盯着看。最后那个LimitNOFILE也别省,隧道一多,文件描述符很容易撞到默认的 1024 上限,撞上之后的症状是"突然所有隧道都不通了,重启就好",非常难查。
2.3 conf/nps.conf 里真正需要动的那几行
配置文件字段不少,但绝大多数保持默认就行。下面是我每次都会过一遍的部分:
appname = nps runmode = prod # Web 管理面板 web_ip = 0.0.0.0 web_port = 8080 web_open_ssl = false # 桥接参数 bridge_type = tcp bridge_port = 8024 bridge_ip = 0.0.0.0 public_vkey = 换成你自己的随机串 # HTTP/HTTPS 转发端口 http_proxy_port = 80 https_proxy_port = 443 https_just_proxy = true # 日志 log_level = info log_path = /var/log/nps.log # 用户权限 allow_user_login = false allow_user_register = false逐条说下为什么这么改。runmode从dev改成prod,是为了关掉一些调试行为、让日志更干净。public_vkey必须是随机长串,我一般用openssl rand -hex 16生成一个,图省事用密码管理器里的随机串也行。log_level保持info就好,调到debug会在流量大的时候把磁盘写满,这个坑我踩过一次,服务器磁盘告警才发现是日志惹的祸。allow_user_register关掉,是防止有人在面板上自助注册账号——面板对公网开着的时候,这个默认值是挺危险的。
改完配置记得systemctl restart nps,然后journalctl -u nps -n 50看一眼有没有报错。配置文件里任何一处语法写错,进程会直接起不来,日志里会告诉你哪一行有问题。
2.4 登进面板之后要做的前三件事
浏览器打开http://公网IP:8080,默认账号密码在发行包的说明里能找到(老版本是admin/123,新版首次启动会随机生成并打印在日志里,具体以你的版本为准)。登进去之后,别急着建隧道,先把这三件事办了:改掉面板密码、确认public_vkey已经换掉、把面板端口从 8080 改成不常见的高位端口。这三步加起来不超过两分钟,但能挡掉绝大多数扫描。
面板里有个"客户端"列表,npc 上线后会出现在这里,能看到它的唯一标识和在线状态。这个页面是后面排查问题的第一个抓手——如果客户端不在这里,说明桥接都没建起来,问题出在网络层;如果客户端在线但业务不通,问题就在隧道配置或内网服务本身。把这两种情况分开,排查效率会高很多。
3. 内网这台机器:npc 接进来的两种姿势
3.1 命令行一把梭:临时验证用这个
只是想确认能不能通,一条命令就够了:
./npc -server=公网IP:8024 -vkey=你的vkey -type=tcp几个参数的含义分别是:-server是服务端的桥接地址,-vkey是身份凭据,-type指定桥接使用的传输方式(可选tcp、kcp、tls,具体支持情况看你的版本)。跑起来之后,服务端面板的客户端列表里应该立刻出现一条新记录。这时候去面板上建一条 TCP 隧道,把服务端口指到 9001、目标地址填127.0.0.1:8000,再从外网访问公网IP:9001,页面能出来就说明整条链路通了。
命令行的方式适合快速验证,缺点也很明显:关掉终端进程就没了,而且参数散在命令里不好维护。验证通过之后,还是要落到配置文件上。
3.2 配置文件方式:长期驻留应该这么写
npc 的配置文件叫npc.conf,写法大致是:
[common] server_addr=公网IP:8024 conn_type=tcp vkey=你的vkey auto_reconnection=true max_conn=1000 flow_limit=1000 rate_limit=1000 # 本地状态查看界面(可选) # web_username=admin # web_password=自定义口令 # web_port=8081 # 日志 log_level=info log_path=/var/log/npc.log重点解释几个字段。auto_reconnection=true一定要打开,它负责在网络抖动、运营商回收连接之后自动重连,没有它就得手工重启客户端,长期挂着的服务基本没法用。max_conn是连接数上限,flow_limit和rate_limit分别是流量和速率的限制,单位是 KB,不填或设得过大等于不限制,具体取值建议按内网服务的实际压力来,比如只给同事看个管理后台,rate_limit=1024就够。
conn_type这个字段值得单独说说。默认的tcp在多数网络环境下表现最好,兼容性也最广。如果客户端所在网络对长连接不太友好,频繁出现断流,可以换成kcp(基于 UDP,抗丢包能力强,代价是流量开销略高),或者换成tls让桥接流量带上加密。这个切换需要服务端bridge_type和客户端conn_type两边保持一致,只改一边会连不上,这是我见过最多的"改了配置就起不来"的原因。
3.3 一台服务端挂多个客户端:命名规范能省很多事
真实的运维场景里,通常不是一台内网机器,而是好几台:办公区一台跑管理后台,机房一台跑数据库,家里一台 NAS。它们可以共用同一个vkey(取决于你的版本是否允许多客户端同凭据),但我更建议给每台机器单独生成一个 vkey,理由是万一某台机器要下线或者凭据泄露,只需要在面板上删掉对应的那条,其他机器完全不受影响。
客户端的备注名建议写成位置-用途-序号的格式,比如office-admin-01、idc-db-02。听起来是小事,但等到面板上挂了三十条客户端记录,你面对一堆npc-1、npc-2的时候,就知道当初多打几个字有多值。隧道命名也一样,我一般直接写成用途-端口,比如admin-9001、ssh-9002,一眼就能对上是哪个服务。
3.4 把 npc 做成开机自启
内网机器多半也是 Linux,同样用 systemd 托管最省心:
[Unit] Description=npc client After=network-online.target Wants=network-online.target [Service] Type=simple WorkingDirectory=/opt/npc ExecStart=/opt/npc/npc -config=/opt/npc/npc.conf Restart=always RestartSec=10 [Install] WantedBy=multi-user.target注意After那里写的是network-online.target而不是network.target。这两者的区别在于,后者只表示网卡驱动加载完了,前者才表示网络真的可用。如果写成前者,机器重启后 npc 可能比网络先起来,连一次失败,虽然Restart=always最终也会把它拉起来,但日志里会多出几行让人误判的报错。这种细节不写进文档,下次换个人维护又要重新踩一遍。
4. 隧道类型怎么选:TCP、UDP、HTTP 与点对点的适用边界
4.1 TCP 隧道:九成场景的默认答案
TCP 隧道是 nps 里最通用的一类。SSH、远程桌面、自建 Web 服务、数据库连接,全都走它。典型配置是:服务端口填9001,目标地址填192.168.1.20:22,外部用户执行ssh -p 9001 用户名@公网IP就能登进内网那台机器。
有个细节值得提醒:服务端口不要选 1024 以下的。Linux 上绑定特权端口需要 root 权限,nps 以普通用户身份跑的时候会直接绑定失败,日志里提示权限不足,很多人看到这个报错会以为是防火墙问题,绕一大圈。另外,如果内网服务的local_ip就是 npc 所在机器本身,填127.0.0.1是最省事的;如果服务在同一个内网的另一台机器上,务必填那台机器的内网地址(比如192.168.1.20),而不是127.0.0.1——这个错误我见过太多次,症状是"客户端在线、隧道也在、访问就是超时"。
4.2 UDP 隧道:视频流、自建服务这类需要单独对待
UDP 隧道的配置方式和 TCP 差不多,但底层行为完全不同。UDP 无连接,nps 需要按来源地址维护会话映射,所以更适合那些自己会做重传和容错的场景,比如自建的流媒体服务、某些游戏服务端、内网 DNS。用 UDP 隧道的时候要留意两件事:一是超时时间,空闲一段时间后会话可能被回收,表现为"过一会儿就连不上,重新发起又好了";二是不要指望它像 TCP 那样保证顺序,如果你的应用本身依赖可靠传输,还是老老实实用 TCP 隧道。我的经验是,能用 TCP 解决的就别用 UDP,除非应用协议本身强制要求。
4.3 HTTP 域名转发:一个 80 端口挂多个站点
这是 nps 比较讨喜的一个功能。做法是:把你的域名 A 记录解析到服务端公网 IP,然后在面板上新建 HTTP 类型隧道,填上域名,服务端会按请求里的 Host 头把流量分发给对应的客户端。这样一来,80 端口只需要开一次,后面挂多少个站点都行——admin.你的域名指到办公区那台机器,nas.你的域名指到家里那台 NAS,互不干扰。
上 HTTPS 的话,把证书文件放到服务端的证书目录,配置里打开web_open_ssl或者在隧道上单独指定证书,https_just_proxy这个开关决定了 HTTPS 流量是直接透传还是由 nps 终止 TLS,两种模式各有取舍:透传省事、证书在内网侧管理;终止 TLS 则由服务端统一处理证书,更新证书只改一处,我通常选后者,因为证书到期的那一刻你会庆幸不用挨个登内网机器。
4.4 私密隧道与点对点:两种省心的玩法
私密隧道适合那种"服务只想给特定几个人看"的场景。建隧道时设一个访问口令,外部用户访问时会先要求输入口令,通过了才转发到内网服务。它的好处是不用在业务应用里额外加一层登录,临时给同事看个后台特别方便。缺点是口令是共享的,谁给了谁你追踪不到,所以只适合短周期使用。
点对点模式解决的是流量成本问题。前面说的所有隧道,数据都要经过公网服务器中转,服务器带宽就是瓶颈。点对点模式的做法是:服务端只负责"牵线",让客户端和访问者尝试直接建立连接,握手成功后数据不再经过服务器。适合大文件传输、视频流这类吃带宽的场景。代价是它依赖双方网络的可达性,如果客户端所在网络做了严格限制,打洞可能失败,这时候会回退到中转模式。配置上需要服务端开放牵线端口,客户端和访问侧也要允许相应流量通过,具体端口和开关按你的版本来,不同版本字段名略有差异,改之前建议先看一眼自带的示例配置。
5. 面板一切正常但就是连不上:几条真实的排查链路
5.1 第一道坎:安全组放行了,系统防火墙还在拦
这是我遇到的最高频问题,没有之一。排查顺序我固定这么走,从内到外一层层剥:
# 1. 本机有没有在监听 ss -tlnp | grep -E '8024|8080|9001' # 2. 本机自己能不能访问 curl -I http://127.0.0.1:8080 # 3. 防火墙规则 firewall-cmd --list-ports # CentOS 系 ufw status verbose # Ubuntu 系 # 4. 从外部网络验证 telnet 公网IP 9001第一步没输出,说明服务根本没起来或者端口写错了,去看journalctl -u nps -n 100。第二步不通,说明被本机防火墙拦了,firewall-cmd --add-port=9001/tcp --permanent && firewall-cmd --reload,或者ufw allow 9001/tcp。第三步规则都在、第四步还是不通,那就去云控制台看安全组,很多时候问题就出在这里——尤其新建实例的时候,安全组模板只放行了 22 和 80。
5.2 第二道坎:客户端在线,访问就是超时
面板上客户端显示绿色在线,隧道也建好了,外部访问公网IP:9001转圈然后超时,这种时候基本可以断定问题出在"npc 到内网服务"这一段。可能的原因按概率排序:local_ip填错了(填成了127.0.0.1但服务在别的机器上)、local_port写错了(比如服务监听 8000 你填了 8080)、内网服务所在机器自己的防火墙没放行这个端口、服务只监听了127.0.0.1而没有监听0.0.0.0。
最后这条特别隐蔽。有些应用默认只绑定回环地址,这种情况下即使防火墙全开、地址全对,从 npc 所在机器访问192.168.1.20:8000依然不通。验证方法很简单:登到 npc 那台机器上curl 127.0.0.1:8000通、curl 192.168.1.20:8000不通,就说明服务只监听了回环地址,需要改应用的监听配置。
5.3 第三道坎:连一阵子就断,断了要等或者要重启
间断性掉线是最折磨人的一类问题,因为刚连上的时候一切正常。我的排查思路是先把"谁断的"分清楚:npc 日志里如果有重连记录,说明是桥接连接断了;如果 npc 日志干干净净,那问题在业务侧。
桥接断的原因通常有三类。一是客户端所在网络对长连接不友好,运营商的 NAT 会话表有老化时间,空闲久了映射就被回收了;二是链路质量差导致丢包累积;三是服务端或客户端其中一端的资源到了上限。对应处理方式:开auto_reconnection、把conn_type从tcp换成kcp、检查LimitNOFILE和max_conn的取值。我有一台放在办公室的机器就是因为办公网络每隔一段时间做一次会话清理,换成kcp之后情况明显好转。
还有一种情况是 TCP 隧道传大文件的时候卡死:小请求都正常,一传几十兆就停住。这类现象往往和链路 MTU 有关,中间某跳对分片包做了处理导致大包被丢弃。我没有遇到过需要直接调 MTU 的极端情况,一般换成kcp或者改用点对点模式就能绕过;如果你确实遇到了,可以在系统层面调小网卡 MTU 试试,但要记得这是权宜之计,链路一变可能又要重调。
5.4 第四道坎:HTTPS 页面能打开但资源全丢
域名转发上线之后经常遇到这种画面:首页 HTML 出来了,样式表、脚本、图片全是 404,或者点任何一个链接都跳回了 IP 地址。这几乎百分百是应用生成了绝对地址导致的。后端框架里的站点地址配置、反向代理传来的协议头,任何一个环节没对齐,应用就不知道自己其实是在 HTTPS 下被访问的。
处理办法有两步。第一步去应用配置里把站点地址显式写成你的域名,别让它自己猜。第二步在 nps 的配置里打开http_add_origin_header,让转发时带上原始请求的信息,这样后端就能判断出真实的访问协议和域名。如果是自己写的服务,记得读X-Forwarded-Proto和X-Forwarded-Host这两个头。
还有个小坑是 DNS 缓存。域名解析改完之后,本地和中间解析节点可能还缓存着旧记录,表现出来就是"我这边能开,别人打不开"。验证的时候用dig 域名 +short直接看解析结果,别依赖浏览器,浏览器缓存加上 DNS 缓存,能让你怀疑人生。
6. 从"能跑通"到"敢交付":面板、日志与备份的收尾工作
6.1 面板安全三件套,两分钟能做完
面板这东西本身就是个对外服务,而且它掌握着所有隧道的开关,安全性优先级应该在所有事之前。我固定做三件事:把web_port从 8080 换成一个不常见的高位端口(比如 4 万以上的随机值);把web_ip从0.0.0.0改成只监听内网地址,再通过其他方式访问,或者至少用云安全组把来源限制在你的固定 IP 上;开启web_open_ssl并配上证书,避免面板口令明文过网。
再补一条容易被忽略的:allow_user_login和allow_user_register都关掉。前者关掉之后,即使有人猜到了账号密码也没有自助入口;后者关掉是防止有人在你的面板上注册账号,然后合法地创建隧道。这两项在面板对公网开放的环境里是必须关的,默认值不能当安全配置用。
6.2 用日志和流量数据回答"谁在用"
服务上线之后,一定会有人问你"这个月跑了多少流量"、"哪台机器占用最多"。nps 面板上有流量统计,log_level=info的日志里也会记录连接情况。我一般会做两件事:一是给每条隧道设上flow_limit和rate_limit,防止某条隧道跑出意外流量;二是给日志配一份轮转规则,避免日志文件失控。
# /etc/logrotate.d/nps /var/log/nps.log { daily rotate 14 compress missingok notifempty copytruncate }最后那行copytruncate是关键。如果日志文件被进程长期持有,直接重命名会导致进程继续往旧文件写,轮转就白做了。copytruncate是先复制再清空,对这类常驻进程最友好。这个细节不写下来,下次谁改了轮转配置,磁盘告警又会回来。
6.3 备份与迁移:一个 conf 目录加一份隧道清单
nps 需要备份的东西其实很少:conf/目录(配置加证书)、nps二进制、以及一份从面板上导出来或者手动记录的隧道清单。面板上的隧道配置存在数据库里(不同版本存储方式不同,有的在conf/下的数据文件里),所以迁移的时候,稳妥做法是把整个 conf 目录打包带走,然后在新机器上还原,再确认一下bridge_port和各类端口在新环境里是否可用。
升级的做法也简单:停服务、备份 conf、替换二进制、启动、看日志。不要只替换二进制不备份——我有一次升级完发现新版本改了配置字段名,老配置直接起不来,靠着备份五分钟回滚,要是没备份就得重头配一遍。升级前先在测试机上跑一遍,这个习惯值得养成。
6.4 资源占用和并发能力的实际感受
最后聊聊容量。nps 本身很轻,空跑的时候 CPU 几乎为零,内存通常几十兆量级,主要开销在连接数和转发带宽上。真正决定一台服务器能挂多少隧道的,不是 nps 本身,而是服务器的带宽和文件描述符上限。我给自己的参考线是:中小规模场景(几十条隧道、每条约几十个并发连接)一台 1 核 2G 的入门机型足够;如果主要是文件传输类流量,瓶颈永远是带宽而不是 CPU。
LimitNOFILE一定要往大了设,我一般写 65535。判断上限是否够用,可以在面板上看实时连接数,或者用ss -s看当前 socket 总量。有个经验性的判断:如果出现"服务跑几小时之后新连接全部失败,重启就好"的现象,八成就是描述符耗尽,这时候先调上限,再回头看是不是有连接泄漏。
需要提醒的是,以上这些数字都来自我自己的使用场景,你的业务类型、内网服务特性、访问者分布都会影响结果,动手之前建议先小规模跑一周,看一段真实的流量曲线再决定扩容策略,比任何估算都靠谱。
从最早只会用命令行npc -server=...硬连,到现在把服务端配置、客户端托管、日志轮转、备份流程整套跑顺,中间踩的坑基本都写在上面了。如果只能记住一句话,那就是:先把端口这张表列清楚,再去改配置。我前后遇到的连接问题里,至少有七成最后都能归结到某个端口没通上,剩下的三成里,一半是local_ip填错。把这两个地方盯住,内网穿透这件事就没什么玄学可言了。