为什么"能打开"不等于"能访问"
很多站长遇到过这种诡异的情况:服务器上 curl 127.0.0.1:8080 一切正常,本地浏览器打开域名却一直转圈,最后报"连接超时"。更奇怪的是,同一台服务器上另一个站点却好好的。这时候大部分人第一反应是"是不是被墙了"、"是不是 CDN 出问题了",然后开始盲目重启 Nginx、换 CDN 节点、甚至重装系统——折腾半天问题依旧。
其实这类问题十有八九出在包过滤防火墙这一层。这篇文章不讲理论空话,而是给出一套可复现的排查路径,覆盖 iptables、nftables、firewalld、ufw 四种常见工具,以及云厂商安全组这个最容易被忽略的"第二层防火墙"。
第一件事:先分清三个层次的"通不通"
在网络排查里,搞清楚"哪一层不通"比"为什么不通"更重要。从内到外分三层:
- 本机回环层:
curl -I http://127.0.0.1:80。这一层通,说明 Web 服务本身在跑、端口在监听。 - 本机对外层:
curl -I http://本机公网IP:80。这一层通,说明本机防火墙放行了该端口。 - 外部到达层:从另一台机器
telnet 公网IP 80。这一层通,说明云安全组、上游路由都没问题。
99% 的"能本地 curl 不能外网访问",卡在第二层或第三层。下面分别说。
第二层排查:本机防火墙到底放行了没有
先确认端口在监听,而且不是只听 127.0.0.1:
ss -lntp | grep -E ':80|:443'如果输出是 127.0.0.1:80,那问题在应用配置(Nginx 的 listen 127.0.0.1:80;),跟防火墙无关。正确应该是 0.0.0.0:80 或 *:80。
确认监听没问题后,看防火墙规则。不同发行版和不同年份装的服务,规则工具可能完全不同,四个都要查一遍才踏实。
iptables(老牌,仍然大量存量)
iptables -L INPUT -n --line-numbers
iptables -t nat -L -n重点看 INPUT 链的默认策略(Policy)。如果是 DROP 或者 REJECT,而上面又没有任何放行 80/443 的规则,那外网必然连不上。补一条即可:
iptables -I INPUT -p tcp --dport 80 -j ACCEPT
iptables -I INPUT -p tcp --dport 443 -j ACCEPT注意这里用 -I(插入到链首)而不是 -A(追加到链尾)。很多人的规则写在一条兜底的 -j DROP 后面,追加了等于没加,这个坑非常常见。
nftables(新版 Debian/Ubuntu 默认)
nft list ruleset
nft list chain inet filter input临时放行:
nft add rule inet filter input tcp dport { 80, 443 } accept没有安装 nftables 却看到有 nft 命令输出,可能是 iptables-nft 兼容层在跑,此时用 iptables 命令查看即可看到同一份规则。
firewalld(CentOS/RHEL 系)
firewall-cmd --state
firewall-cmd --list-all
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload记得加 --permanent 再 reload,只执行不带 permanent 的命令重启后就丢了。
ufw(Ubuntu 简化层,底层仍是 iptables)
ufw status verbose
ufw allow 80/tcp
ufw allow 443/tcpufw 的问题是它会把规则转成 iptables 的 ufw-user-input 链,所以用 iptables -L 直接看会有点乱。以 ufw status 的输出为准。
第三层排查:云安全组才是最常见的元凶
如果你用的是阿里云、腾讯云、AWS、Oracle Cloud、Hetzner 这类云主机,那么除了操作系统里的防火墙,云平台还有一层安全组。这层在宿主机外侧,操作系统里怎么折腾都看不到。
典型症状:iptables -L 全是 ACCEPT,ufw 也是 inactive,本机 curl 正常,但外部就是连不上,而且 ping 通(ICMP 放行)但 80 端口被丢包。
排查方法很简单——去云控制台看入站规则有没有放行 80/443,或者用云厂商的 CLI。这个问题的迷惑性在于:很多新手建站教程只教了配 Nginx,从没提安全组,导致第一次踩坑时完全没有排查方向。
用抓包确定丢包发生在哪一端
当你不确定是"包根本没到服务器"还是"到了服务器被本机丢弃"时,抓包是最直接的证据。
在服务器上执行:
tcpdump -nn -i eth0 tcp port 80然后在外部发起访问。观察结果:
- 完全没有任何包:包在到达服务器前就被丢了——云安全组、上游路由、或者 ISP 层面。此时本机防火墙是无辜的。
- 看到 SYN 进来,但没有 SYN-ACK 出去:包到了,被本机防火墙或应用层拦了。此时查 iptables/nftables 的计数,
iptables -L -v -n里 DROP 规则的 pkts 会涨。 - 看到 SYN 和 SYN-ACK 都出去,但客户端还是超时:回程被拦,检查云安全组出站规则或客户端网络。
抓包这一招能把你从"猜"变成"看",省下大量瞎折腾的时间。建议任何一次网络不通的排查都先跑一条 tcpdump,一分钟就能定位方向。
一个真实的排查案例复盘
场景:某站长迁移服务器后,新机器 SSH 能连、ping 通,但网站 80 端口外部访问超时。
排查过程:
ss -lntp确认 Nginx 监听0.0.0.0:80,本地 curl 返回 200 —— 应用层正常。iptables -L INPUT -n显示策略 ACCEPT,无限制规则 —— 本机防火墙正常。ufw status为 inactive —— 排除 ufw。- 在服务器上
tcpdump -nn tcp port 80,外部访问时一个包都没看到。 - 结论明确:包根本没到服务器。去云控制台检查安全组,发现入站规则里只有 22 端口放行了 80 漏掉。
- 补上 80/443 入站规则,秒通。
整个排查用了不到十分钟,而这十分钟里有一半是花在抓包确认"包到底有没有到"上。如果没有这一步,很可能就去 Nginx 配置和系统防火墙里绕圈子了。
容易被忽略的四个细节
第一,IPv6 是独立的规则体系。用 ip6tables -L 看,或者 nftables 里的 inet 表。你只在 IPv4 放行了 80,用户走 IPv6 访问照样超时,而且 curl 默认会优先尝试 IPv6,报错信息和 IPv4 超时看起来一样。
第二,Docker 会自己改 iptables。只要你 docker run -p 80:80,Docker 就会往 nat 表和 filter 表插规则。如果你手动写了 -A INPUT -j DROP 的兜底策略,Docker 容器的端口可能表现得很奇怪——有时候能通有时候不通,取决于规则插入顺序。容器化的网站,排查时要特别看 DOCKER 和 DOCKER-USER 这两个链。
第三,fail2ban 会误封自己的监控 IP。它往 iptables 里塞的是 DROP 规则,位置往往在链首,优先级比你自己的放行规则还高。如果某个 IP 突然访问不了而其他 IP 正常,先 fail2ban-client status sshd 看看封禁列表。
第四,iptables 规则不持久。命令行加的规则重启就没了。Debian/Ubuntu 用 iptables-persistent,CentOS 用 iptables-services,或者干脆把规则写进 /etc/nftables.conf。加完规则不落盘,等于白干。
收尾:建立一套自己的排查清单
把上面的内容浓缩成一份可执行的清单,下次遇到端口不通,按顺序过一遍,基本不会漏:
ss -lntp—— 端口在听吗?听得是 0.0.0.0 还是 127.0.0.1?curl 127.0.0.1—— 应用层自己能通吗?iptables -L -v -n/nft list ruleset/firewall-cmd --list-all/ufw status—— 本机防火墙放行了吗?ip6tables -L—— IPv6 是不是漏了?- 云控制台安全组 —— 入站放行了这个端口吗?
tcpdump -nn -i eth0 tcp port N—— 包到底有没有到?
这套流程的价值在于:每一步都能给出一个明确的"是/否"判断,把模糊的"网站打不开"拆解成六个可以独立验证的问题。运维排查最怕的不是技术难,而是方向错——有了顺序清晰的清单,你就不用再靠重启和重装来碰运气了。
最后提醒一句:改防火墙规则时,务必保持一个已登录的 SSH 会话不要断开。很多服务器的第一条生产事故,就是加了 DROP 兜底规则顺手把自己也挡在外面,只能去控制台开 VNC 救援。开 VNC 之前先确认规则可用,这是老站长的基本纪律。