返回博客

nginx 本地 200、外网超时——根在云安全组不在防火墙

在一台新开的云主机上装好 nginx,本地 curl localhost/ 返回 200,心想万事大吉——打开浏览器输入公网 IP,转圈、超时。换 curl 来试:

curl: (28) Connection timed out after 10001 milliseconds

或者有时候是:

curl: (52) Empty reply from server

两种报错看起来截然不同,结果我在 nginx 配置、iptables、firewalld、SELinux 上绕了一大圈,全部正常。

现象

四项诊断同时成立:

  1. systemctl is-active nginxactive
  2. ss -tlnp | grep :80 显示监听在 0.0.0.0:80,不是 127.0.0.1
  3. VM 内 curl http://localhost/ 返回 HTTP 200
  4. tail -f /var/log/nginx/access.log 只有 ::1127.0.0.1 的记录——外部请求从未出现在日志里

第 4 条是最关键的信号:如果外网请求连 access.log 都没进来,说明数据包根本没到达 VM 的网络栈,被上游拦掉了。

根因

云主机的网络访问控制分两层:

| 层 | 位置 | 工具 | |---|---|---| | 云安全组(Security Group) | 云平台 VPC 层,VM 外部 | 控制台 / CLI | | 本地防火墙 | VM 内部 | iptables / firewalld / ufw |

新建实例默认只开 SSH/22。HTTP/80 和 HTTPS/443 默认关闭,与本地 iptables 规则无关。iptables 全 ACCEPT 只代表 VM 内部不拦,拦截发生在更上游的 VPC 层,iptables 根本看不到这些包。

坑:iptables ACCEPT + firewalld inactive,外网仍然不通

本地防火墙工具只管 VM 内部的包过滤。云平台安全组是独立的 SDN/VPC 层拦截,状态完全不同步。两个"防火墙"互相独立,都放行才能通。

curl (52) Empty replycurl (28) Connection timed out 是同一个安全组规则缺失的两种表现——前者是 TCP 握手后 RST,后者是 SYN 被静默丢弃,取决于客户端路由路径,不必当成两个不同问题排查。

正解

第一步:确认是安全组问题

在 VM 上跑诊断四连:

# 确认监听 0.0.0.0
ss -tlnp | grep :80
 
# 确认本地 200
curl -sS -o /dev/null -w "%{http_code}\n" http://localhost/
 
# 确认 iptables 无 DROP
iptables -L INPUT -n --line-numbers | head
 
# 看日志有无外部 IP
tail -10 /var/log/nginx/access.log

从另一台机器(或手机热点)打外网:

curl -sS --max-time 10 -o /dev/null \
  -w "HTTP %{http_code} time=%{time_total}s\n" \
  http://<公网IP>/
# 如果输出 000 + 超时 → 安全组未放行

第二步:在云控制台放行端口

阿里云 ECS(控制台路径): ECS 控制台 → 安全组 → 入方向规则 → 添加规则:协议 TCP,端口 80/80,来源 0.0.0.0/0

阿里云 CLI:

aliyun ecs AuthorizeSecurityGroup \
  --SecurityGroupId sg-xxx \
  --IpProtocol tcp \
  --PortRange 80/80 \
  --SourceCidrIp 0.0.0.0/0 \
  --region cn-hangzhou

AWS CLI:

aws ec2 authorize-security-group-ingress \
  --group-id sg-xxx \
  --protocol tcp --port 80 \
  --cidr 0.0.0.0/0

GCP:

gcloud compute firewall-rules create allow-http \
  --allow tcp:80 --source-ranges 0.0.0.0/0

第三步:验证

# 外网再打一次
curl -sS -o /dev/null -w "HTTP %{http_code}\n" http://<公网IP>/
# 期望:HTTP 200
 
# 日志中应出现真实客户端 IP
tail -1 /var/log/nginx/access.log
正解:去云控制台开安全组入方向 TCP 80/443

安全组规则是实时生效的,不需要重启 nginx 或任何服务。放行后外网立刻可达,access.log 里也会立即出现客户端真实 IP。

补充:SELinux 场景

如果以上都做了还不通,在 RHEL / AlmaLinux / CentOS 系机器上再查一层:

getenforce  # 若为 Enforcing
ausearch -m avc -ts recent | grep nginx

SELinux 可能阻止 nginx 绑定非标准端口或访问特定目录。

一句话外卖

云主机「本地通、外网不通」先查云平台安全组,access.log 没有外部 IP = 包没进 VM,nginx/iptables 都是无辜的。