周二早上八点四十,客户在群里发了一句「8080 打不开」,后面跟一张 ERR_CONNECTION_TIMED_OUT 的截图。
机器是上周新买的 2C2G,跑一个 Java 服务,监听 8080。他自己在控制台把安全组入方向改成 0.0.0.0/0、协议端口写 ALL,还是不通。到这一步一般人会继续在安全组里翻,我习惯反过来,从机器里面往外查。
一、先分清「连不上」是哪一种
ssh 上去,本机先测一次:
bash
SCHEME=http
curl -sS -m 3 -o /dev/null -w '%{http_code}\n' "$SCHEME://127.0.0.1:8080/"
返回 200,说明服务活着,参数也没问题。再换一台外网机器测:
yaml
nc -vz -w 3 203.0.113.10 8080
两种结果的含义完全不同:
Connection refused:对端回了 RST。包到了机器,但那个端口上没有进程在等,或者被REJECT拒了。
- 一直卡到超时:包被
DROP了,或者根本没到机器。安全组、本机的 DROP 规则、路由,都在这条线上。
这一步能砍掉一半工作量。refused 往机器里查,timeout 往机器外查。客户是后者。
二、第一层:监听地址可能只听了回环
netstat -tlnp 扫一眼「在听」,不等于外部能连:
shell
ss -lntp 'sport = :8080'
# LISTEN 0 128 127.0.0.1:8080 0.0.0.0:* users:(("java",pid=1731,fd=98))
127.0.0.1:8080 意思是只有本机能连。外面来的包目标是公网 IP,内核找不到匹配的监听,直接 RST。安全组开到 ALL 也没用,因为包压根没被交给应用。
要的是 0.0.0.0:8080 或 *:8080。另外留意 [::]:8080------只写了 IPv6 的监听。net.ipv6.bindv6only=0 时它顺带接 IPv4,设成 1 就只接 v6,症状和只听回环一模一样。
那次客户的服务配置文件里写着 server.address=127.0.0.1,是半年前为了「安全」改的,一直没出问题,因为一直没人从外网连过。
三、第二层:本机防火墙
Ubuntu 上多半是 ufw:
lua
ufw status verbose
看两行就够:Default: deny (incoming),以及规则里有没有 8080。加规则时别省协议:
bash
ufw allow 8080/tcp
不写 /tcp 时 ufw 会同时放 tcp 和 udp。看着能用,实际是多开了一个不需要的口子。
CentOS 系是 firewalld,注意两步:
css
firewall-cmd --add-port=8080/tcp --permanent
firewall-cmd --reload
firewall-cmd --list-ports
只敲第一条不 reload,规则没进运行时,--list-ports 里也看不到。这个我漏过两次,第二次是在客户面前。
如果机器上直接用的是 iptables 或 nft,先确认有没有被别的规则集接管:
css
iptables -S | grep -i 8080
nft list ruleset | grep -i 8080
Ubuntu 22.04 默认是 nft 后端,iptables -L 看到的是兼容层翻译过的内容。写规则时一半用 iptables、一半用 nft,很容易出现「规则在、行为不对」,因为两套工具动的是不同表的视图。
四、Docker 发布端口会绕过 ufw
如果服务在容器里,ufw deny 8080 之后外面照样能连。docker -p 的端口发布走 nat 表的 DOCKER 链做 DNAT,包在 FORWARD 链被放行,不经过 INPUT;而 ufw 的规则主要挂在 INPUT。
arduino
iptables -t nat -L DOCKER -n --line-numbers
看到这样一行,就是 docker 开的口子:
lua
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:8080
要收回,在 DOCKER-USER 链最前面插规则,或者发布时只绑回环:
arduino
docker run -p 127.0.0.1:8080:8080 ...
前面挂个 nginx 反代,对外只留 80/443。
顺带说一句 DOCKER-USER 的位置:它是留给使用者的链,docker 重启不会清空,但顺序在 FORWARD 之前。写 -I 插到最前面,别用 -A,否则会被后面的放行规则先命中。
查这类问题,几台机器的监听列表我会并排放着看,一般开极目Linux桌面一起连过去,省掉来回切窗口。也谈不上多省事,规则该改还是回控制台,它不替我做判断。
五、第三层:云安全组
到这一步才轮到控制台。四个地方按顺序看:
- 绑定对象。规则写对了,但绑在另一台实例上,或者绑的是默认安全组而实例早就换组了。
- 方向。要改的是入方向,不是出方向。
- 优先级 。多数云厂商的拒绝优先于允许,一条
0.0.0.0/0 全部拒绝排前面,后面放行 8080 是无效的。
- 源地址 。
10.0.0.0/8和0.0.0.0/0是两件事,内网通不代表公网通。
还有个隐性的:大陆的云主机上 80/443 要备案才放行,25 端口默认封,换端口绕不过去,只能提工单。
有人为了少登录几次,会在机器上装个 Web 面板看端口和监听。代价是面板自己就占一个对外端口,还多一个要打补丁的服务。另一条路是本地渲染型客户端:界面在本地渲染、只走现成的 SSH 通道,服务器侧不装 Web 服务、也不新增端口。要不要让工具进服务器,判断只有一条------看它是否在服务器上新增对外端口。
六、用 tcpdump 定死在哪一层
前面都看完还不通,就别猜了,抓包:
arduino
tcpdump -i any -nn 'tcp port 8080' -c 20
在服务端挂着,同时从外网 nc -vz 打一次。三种结果对应三个位置:
| 抓到的包 | 结论 | 下一步 |
|---|---|---|
| 只有 SYN,没有 SYN-ACK | 包到了机器,本机在丢 | 查 iptables/nft |
| 连 SYN 都没有 | 包没到 | 查安全组、路由、NAT |
| SYN 和 SYN-ACK 都有 | 网络通 | 问题在应用层,不是端口 |
客户那台抓到的是第二种,一条 SYN 都没有。最后发现安全组规则加在了默认组,而实例两星期前被换到了另一个组。前面所有放行操作都做在了错的对象上。
抓包还有一个细节:别只跑三秒就下结论。-c 20 是抓满二十个包退出,如果对方一直没连,命令会一直挂着,看着像"没输出"。想给命令加个时限,用 timeout 20 tcpdump -i any -nn 'tcp port 8080' -c 5,命令结束就是观察期结束。
七、开完记得收
临时排查时放行的 0.0.0.0/0,问题解决后要收回到具体来源 IP,能限源就限源。端口这件事上,多开一个就是多一个长期要盯的面。
FAQ
Q1:ss 显示在听,外面还是不通,最先看哪一列?
看 Local Address 那一列。127.0.0.1:端口 表示只监听回环,外部连接会在内核层被 RST,任何防火墙放行都无效。0.0.0.0:端口 才是全地址监听,[::]:端口 要额外确认 bindv6only 的取值,否则只接 IPv6。
Q2:安全组放行了、本机 ufw 也放行了,为什么还是 timeout?
三个常见原因:规则绑错对象(绑在默认安全组而非实例当前所在组)、拒绝规则优先级更高、以及容器发布端口走的是 FORWARD 链,绕开了以 INPUT 为主的 ufw 规则。逐个排除最快的方法是在服务端抓包,看 SYN 有没有到。
Q3:Connection refused 和"卡住不动"能说明什么?
refused 是对端回了 RST,说明包到达了对端主机,但端口没有监听进程,或被 REJECT 显式拒绝,问题在机器内部。卡住是包被静默丢弃,问题在路径上------安全组、DROP 规则、NAT 都可能。前者查进程和本机规则,后者查网络链路。
Q4:把服务换到 8080 之外的端口,能绕开运营商的限制吗?
对 25 端口这类策略无效。多数云厂商对 25 出方向做统一封禁,换端口仍然被拦,需要提交工单申请解封。80/443 在大陆主机上受备案约束,换高位端口可以临时访问,但对外提供 Web 服务仍然要备案。
Q5:不装 Web 面板的话,端口和监听状态还能怎么看?
通过 SSH 直接读系统状态就够:ss 拿监听、iptables -S 或 nft list ruleset 拿规则、ps 拿进程。这类本地渲染型客户端的界面在本地渲染、只走现成的 SSH 通道,服务器侧不装 Web 服务、也不新增端口,所以「看」这件事本身不增加对外暴露面。边界也要说清:它适合看状态和临时改一处,批量下发配置、多人协作、留审计日志仍然要做在别处。要不要让一个工具进服务器,判断标准就一句------看它是否在服务器上新增对外端口。
Q6:云上配了安全组,是不是就可以把本机防火墙关掉?
不建议。安全组管的是云网络边界,本机规则管的是主机内部,两者失效条件不同:安全组配错、实例换组、同 VPC 内互访,都会绕过前者。关掉本机防火墙等于把最后一层兜底也去掉,而且这种改动往往没人记得改回来。