FAQ-12:连接被拒绝(connection refused)

预计阅读时间:19 分钟

📖 目录

问题速查表

问题解决章节
目标端口没有进程在监听第一步:确认目标端口是否有进程监听、启动或重启服务
防火墙规则主动拒绝(REJECT)连接第二步:检查防火墙规则、配置防火墙放行规则
服务只绑定 127.0.0.1,外部无法连接第三步:检查服务绑定地址、修改服务监听地址
客户端无法确认端口连通性第四步:测试端口连通性
Docker 容器端口映射不正确第六步:检查 Docker 容器端口映射、修复 Docker 端口映射
SELinux/AppArmor 阻止服务监听端口原因分析

Connection refused 是网络故障排查中最常见的错误之一。当你尝试连接远程服务器的某个端口时,如果收到这个错误,意味着客户端成功发送了 TCP SYN 连接请求,但目标主机在该端口上没有进程在监听,或者防火墙主动发送了 RST(重置)包拒绝了你的连接。这个错误在日志中通常表现为 connect to IP port port: Connection refused

理解 Connection refused 的关键在于区分它与其他网络错误的不同。它说明网络层是通的(IP 可达、路由正常),但应用层没准备好(服务未启动、未监听正确端口)或被主动拒绝(防火墙规则、安全组策略)。这与 Connection timed out(网络不通或被 DROP)和 Network unreachable(路由不可达)有本质区别。

这个错误在各种场景中都会出现:部署新服务后端口配置错误、MySQL/Redis 远程连接失败、Docker 容器端口映射问题、Nginx 反向代理目标不可达、防火墙规则变更后服务中断等。系统性地排查 Connection refused 需要从服务端监听状态、防火墙规则、服务绑定地址三个维度入手。掌握正确的排查顺序和工具使用方法,可以快速定位并解决问题。

原因分析

服务未启动或未正常运行是最常见的原因。目标端口上没有进程在监听,客户端的连接请求自然被拒绝。服务可能因为配置错误、依赖缺失、资源不足等原因启动失败。

服务绑定地址错误也会导致此问题。如果服务只绑定在 127.0.0.1(本地回环地址),外部客户端就无法连接。这在 MySQL、Redis 等数据库服务的默认配置中非常常见,它们出于安全考虑默认只监听本地。

防火墙规则是另一个主要原因。防火墙可以配置两种拒绝方式:REJECT(主动返回 RST 包,客户端收到 Connection refused)和 DROP(静默丢弃,客户端连接超时)。REJECT 模式下客户端会立即收到拒绝响应。

Docker 容器网络隔离在容器化环境中很常见。容器内的服务可能监听了容器内部的端口,但没有正确映射到宿主机端口。此外,容器之间默认使用桥接网络,直接使用 localhost 连接会失败。

SELinux/AppArmor 安全策略也可能阻止服务监听特定端口,即使服务配置本身是正确的。这在 CentOS/RHEL 系统上尤为常见。

排查决策树

Connection refused 的排查遵循"从本机到跨机、从服务到防火墙"的分层思路,按下面的决策树逐层排除:

连接报错:Connection refused
│
├─ ① 先在本机测试(排除网络因素)
│    │    curl/nc 127.0.0.1 端口
│    │    ├─ 本机也 refused → 服务没起或没在监听 → 进入 ②
│    │    └─ 本机正常 → 进入 ③
│
├─ ② 检查服务状态与监听地址
│    │    ss -tlnp | grep 端口
│    │    ├─ 无输出 → systemctl status 服务 → 启动 / 查日志
│    │    └─ 有输出但显示 127.0.0.1:端口 → 只绑定本机
│    │         └─ 修改服务监听地址为 0.0.0.0 并重启
│
├─ ③ 跨机测试
│    │    nc -zv 目标IP 端口
│    │    ├─ 得到 refused → 网络层是通的,被主动拒绝 → 进入 ④
│    │    └─ 得到 timeout → 网络不通或被 DROP → 查防火墙 DROP 规则、
│    │         安全组、路由、目标主机网络状态
│
└─ ④ 检查防火墙与服务侧安全策略
     ├─ nft list ruleset / ufw status / firewall-cmd --list-all
     │    └─ 找到 REJECT 规则 → 放行目标端口
     ├─ SELinux/AppArmor 是否阻止(ausearch -m avc / aa-status)
     └─ Docker 场景:docker ps 检查端口映射是否缺失

排查步骤

第一步:确认目标端口是否有进程监听

# 在目标服务器上执行
ss -tlnp | grep :端口号

# 或使用 lsof
lsof -i :端口号

# 或使用 netstat(已废弃,需安装 net-tools,建议优先使用 ss)
netstat -tlnp | grep :端口号

如果以上命令没有输出,说明该端口确实没有进程在监听。需要检查服务是否已启动:systemctl status 服务名

第二步:检查防火墙规则

# 查看 nftables 规则(现代系统推荐)
sudo nft list ruleset

# 查看 iptables 规则
sudo iptables -L -n -v

# 查看 ufw 状态(Ubuntu)
sudo ufw status verbose

# 查看 firewalld 状态(CentOS/RHEL)
sudo firewall-cmd --list-all

注意区分 REJECT 和 DROP:REJECT 会返回 RST 包,客户端收到 Connection refused;DROP 则静默丢弃,客户端连接超时。检查是否有针对目标端口的 REJECT 规则。

第三步:检查服务绑定地址

ss -tlnp | grep 服务名
# 如果显示 127.0.0.1:端口,服务只监听本地
# 需要改为 0.0.0.0:端口 才能接受外部连接

# 例如 MySQL 配置
cat /etc/mysql/mysql.conf.d/mysqld.cnf | grep bind-address
# 默认:bind-address = 127.0.0.1
# 改为:bind-address = 0.0.0.0

第四步:测试端口连通性

# 从客户端测试(推荐使用 nc)
nc -zv 目标IP 端口

# 使用 telnet
telnet 目标IP 端口

# 使用 curl 测试 HTTP/HTTPS
curl -v http://目标IP:端口

# 使用 nmap 扫描端口状态
nmap -p 端口 目标IP

第五步:检查服务日志

# 查看服务最近的日志
sudo journalctl -u 服务名 -n 50 --no-pager

# 查看系统日志中的连接拒绝记录
sudo dmesg | tail -20
sudo grep -i "refused\|reject" /var/log/syslog

第六步:检查 Docker 容器端口映射

# 查看容器端口映射
docker ps --format "table {{.Names}}\t{{.Ports}}"

# 确认容器内服务监听地址
docker exec 容器名 ss -tlnp

# 测试容器网络连通性
docker exec -it 另一个容器名 ping 目标容器名

# 查看 Docker 网络配置
docker network ls
docker network inspect bridge

解决方案

启动或重启服务

# 启动服务
sudo systemctl start 服务名

# 设置开机自启
sudo systemctl enable 服务名

# 查看服务状态
sudo systemctl status 服务名

修改服务监听地址

# 以 MySQL 为例
# 编辑配置文件
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
# 修改 bind-address 为 0.0.0.0

# 重启服务
sudo systemctl restart mysql

# 验证
ss -tlnp | grep 3306

配置防火墙放行规则

# UFW(Ubuntu)
sudo ufw allow 3306/tcp
sudo ufw reload

# nftables
sudo nft add rule inet filter input tcp dport 3306 accept

# iptables
sudo iptables -A INPUT -p tcp --dport 3306 -j ACCEPT

# firewalld(CentOS)
sudo firewall-cmd --permanent --add-port=3306/tcp
sudo firewall-cmd --reload

修复 Docker 端口映射

# 重新创建容器并映射端口
docker run -d -p 3306:3306 --name mysql-container mysql:8

# 或修改 docker-compose.yml
# ports:
#   - "3306:3306"

常见场景速查

场景原因解决方案
服务刚重启完连不上进程还没完全启动等几秒重试,用 systemctl status 确认
远程连 MySQL/Redis 失败服务只绑定 127.0.0.1修改 bind-address 为 0.0.0.0
防火墙开启后连不上防火墙规则拒绝了端口添加放行规则:ufw allow 端口
容器内连宿主机失败容器网络隔离用宿主机 IP(通常是 172.17.0.1)而非 localhost
HTTPS 连接被拒443 端口未开放或证书问题检查防火墙和 Nginx/Apache SSL 配置
SELinux 阻止服务安全策略限制sudo setsebool -P httpd_can_network_connect 1

真实案例

案例 A:服务只绑定 127.0.0.1,外部连接一律 refused

现象:开发者在服务器(10.0.0.5)上启动了 Node.js API 服务(端口 8080),本机 curl 一切正常,但远程客户端始终连不上:

# 远程客户端
$ curl -v http://10.0.0.5:8080/api/health
*   Trying 10.0.0.5:8080...
* connect to 10.0.0.5 port 8080 failed: Connection refused
* Failed to connect to 10.0.0.5 port 8080 after 0 ms

# 服务器本机(居然正常)
$ curl http://127.0.0.1:8080/api/health
{"status":"ok"}

排查过程:本机可访问、远程 refused,第一反应是查服务监听地址:

$ ss -tlnp | grep 8080
LISTEN 0 511 127.0.0.1:8080 0.0.0.0:* users:(("node",pid=18234,fd=21))

根因ss 输出的监听地址是 127.0.0.1:8080,服务只绑定了回环接口,外部流量根本无法到达。代码里写死了绑定地址:

app.listen(8080, '127.0.0.1');   // 错误:只监听本机回环

修复:改为监听所有接口(或按安全要求绑定业务网卡的具体 IP):

app.listen(8080, '0.0.0.0');     // 监听所有接口
# systemd 部署场景:修改 unit 文件中的 ExecStart 参数后
sudo systemctl daemon-reload && sudo systemctl restart myapp

验证

$ ss -tlnp | grep 8080
LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=18234,fd=21))
$ curl http://10.0.0.5:8080/api/health    # 远程访问成功
{"status":"ok"}

注意:对公网暴露服务前务必先确认防火墙规则,避免端口直接裸露在公网上。

案例 B:防火墙 REJECT 与服务未启动的报错辨析

现象:两台服务器 A(10.0.0.10)访问 B(10.0.0.11)的 3306 端口失败,运维在 A 上连续做了三组测试,得到三类截然不同的报错:

# 测试一:B 上防火墙有 REJECT 规则(服务其实在监听)
$ nc -zv 10.0.0.11 3306
nc: connect to 10.0.0.11 port 3306 (tcp) failed: Connection refused

# 测试二:B 上防火墙 DROP(服务未启动)
$ nc -zv 10.0.0.11 3306
nc: connect to 10.0.0.11 port 3306 (tcp) timed out

# 测试三:B 上无防火墙规则(服务未启动)
$ nc -zv 10.0.0.11 3306
nc: connect to 10.0.0.11 port 3306 (tcp) failed: Connection refused

根因辨析:测试一和测试三的报错文字完全相同,但原因截然不同,必须靠服务端证据区分:

# 测试一对应的服务端检查:服务在监听,是防火墙拒绝
$ ss -tlnp | grep 3306
LISTEN 0 70 0.0.0.0:3306 ...            # 服务其实活着
$ sudo nft list ruleset | grep 3306
tcp dport 3306 reject                   # ← 罪魁祸首:REJECT 规则

# 测试三对应的服务端检查:服务根本没起来
$ ss -tlnp | grep 3306                  # 无任何输出
$ systemctl status mysql
● mysql.service - MySQL Community Server
   Loaded: failed (Result: exit-code)

修复:测试一执行放行命令 sudo nft add rule inet filter input tcp dport 3306 accept(或对应 ufw/firewalld 命令);测试三先修复 MySQL 配置再 systemctl start mysql

关键结论:Connection refused 只说明"连接被拒绝"——服务未启动、防火墙 REJECT 都会产生它;Connection timed out 说明"被丢弃"——防火墙 DROP、安全组、路由问题。判断依据永远是先看服务端 ss -tlnp,再对照防火墙规则,两者结合才能定性。

案例 C:Docker 容器端口映射遗漏导致外部无法访问

现象:Docker 容器内 Nginx 正常运行,curl localhost:80 返回 200,但外部访问服务器 IP:8080 报 Connection refused。

# 容器内测试正常
$ docker exec web curl -s localhost:80
<html>...nginx welcome page...</html>

# 外部访问失败
$ curl http://10.0.0.5:8080
curl: (7) Failed to connect to 10.0.0.5 port 8080: Connection refused

# 检查容器端口映射
$ docker port web
# 无输出——没有端口映射!

$ docker ps
CONTAINER ID  IMAGE  PORTS               NAMES
a1b2c3d4e5f6  nginx                          web

根因:docker run 时没有使用 -p 参数映射端口,容器内的 80 端口没有暴露到宿主机。

修复:重新创建容器并添加端口映射:docker run -d -p 8080:80 --name web nginx。检查 docker ps 的 PORTS 列确认映射成功。

案例 D:Nginx 反向代理目标服务不可达导致 502

现象:Nginx 反向代理配置后,访问网站返回 502 Bad Gateway,但后端服务实际在运行:

# 客户端访问
$ curl -v http://10.0.0.5/
* Connected to 10.0.0.5 (10.0.0.5) port 80
> GET / HTTP/1.1
< HTTP/1.1 502 Bad Gateway
< Server: nginx/1.18.0
...
502 Bad Gateway
nginx/1.18.0

排查过程:检查 Nginx 配置和后端服务状态:

# 查看 Nginx 配置中的 upstream
$ grep -A5 "upstream" /etc/nginx/conf.d/app.conf
upstream backend {
    server 127.0.0.1:8080;
}

# 检查后端服务是否在监听
$ ss -tlnp | grep 8080
# 无输出——后端服务未启动

# 启动后端服务后验证
$ systemctl start myapp
$ ss -tlnp | grep 8080
LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:(("myapp",pid=1234,fd=6))

根因:Nginx 配置的 upstream 指向 127.0.0.1:8080,但后端服务未启动。Nginx 无法连接到上游服务器,返回 502 错误。

修复:启动后端服务并设置开机自启。同时优化 Nginx 配置添加健康检查:

upstream backend {
    server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
    keepalive 32;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_next_upstream error timeout http_502 http_503;
        proxy_connect_timeout 5s;
    }
}

预防:使用 systemctl enable 服务名 设置服务开机自启。配置 Nginx 健康检查和故障转移机制。

案例 E:SSH 端口被防火墙 DROP 导致连接超时(与 Connection refused 的区别)

现象:运维修改防火墙规则后,SSH 连接从正常变为超时:

# 修改前正常
$ ssh -v 10.0.0.11 2>&1 | head -5
OpenSSH_8.9p1 Ubuntu-3
debug1: Connecting to 10.0.0.11 port 22.
debug1: Connection established.

# 修改后超时
$ ssh -v 10.0.0.11 2>&1 | head -5
OpenSSH_8.9p1 Ubuntu-3
debug1: Connecting to 10.0.0.11 port 22.
debug1: connect to 10.0.0.11 port 22: Connection timed out

排查过程:Connection timed out 与 Connection refused 有本质区别:

# Connection refused = 目标端口有服务但拒绝(或无服务)
$ nc -zv 10.0.0.11 22
nc: connect to 10.0.0.11 port 22 (tcp) failed: Connection refused

# Connection timed out = 连接被静默丢弃
$ nc -zv 10.0.0.11 22
nc: connect to 10.0.0.11 port 22 (tcp) failed: Connection timed out

# 检查防火墙是否有 DROP 规则
$ sudo iptables -L -n | grep 22
DROP  tcp  --  0.0.0.0/0  0.0.0.0/0  tcp dpt:22

根因:防火墙使用了 DROP(静默丢弃)而非 REJECT(主动拒绝)策略。客户端发送的 SYN 包被静默丢弃,没有收到任何响应,导致连接超时。

修复

# 删除 DROP 规则
$ sudo iptables -D INPUT 1

# 或添加 ACCEPT 规则(优先级更高)
$ sudo iptables -I INPUT -p tcp --dport 22 -j ACCEPT

# 持久化规则
$ sudo iptables-save > /etc/iptables/rules.v4

预防:理解 DROP 与 REJECT 的区别。调试时使用 REJECT 快速获得反馈,生产环境根据安全需求选择。修改防火墙规则前先备份当前规则。

案例 F:容器间 Docker 网络隔离导致服务互访失败

现象:两个 Docker 容器在同一宿主机上,但 A 容器无法连接 B 容器:

# 在 A 容器中
$ docker exec container-a curl http://container-b:8080
curl: (7) Failed to connect to container-b port 8080: Connection refused

# B 容器内服务正常
$ docker exec container-b curl localhost:8080
{"status":"ok"}

排查过程

# 查看容器网络
$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
abc123...      bridge    bridge    local
def456...      host      host      local

# 检查容器是否在同一网络
$ docker inspect container-a --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
172.17.0.2

$ docker inspect container-b --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
172.17.0.3

# 尝试用 IP 连接
$ docker exec container-a curl http://172.17.0.3:8080
{"status":"ok"}

# 用容器名连接失败的原因:未加入同一用户定义网络

根因:两个容器虽然都在默认的 bridge 网络中,但默认 bridge 网络不支持容器名解析。只有用户定义的自定义网络才支持 DNS 服务发现。

修复

# 创建自定义网络
$ docker network create app-net

# 将两个容器加入同一网络
$ docker network connect app-net container-a
$ docker network connect app-net container-b

# 现在可以用容器名互访
$ docker exec container-a curl http://container-b:8080
{"status":"ok"}

预防:Docker Compose 中的 services 默认会创建共享网络。手动部署容器时,显式创建自定义网络并加入所有需要互访的容器。

预防措施

  • 部署服务后立即验证监听地址和端口:ss -tlnp | grep 端口号
  • 使用配置管理工具(Ansible、Terraform)统一管理防火墙规则,避免手动配置遗漏
  • 编写服务健康检查脚本,定期检测关键端口的可达性
  • Docker 容器部署后在 docker-compose.yml 中明确声明端口映射
  • systemd 服务的 unit 文件里显式指定监听地址(如 ExecStart 参数带 0.0.0.0),防止默认 127.0.0.1 造成线上事故
  • 防火墙规则变更走评审流程:先在测试环境验证,变更后立即用 nc -zv 做回归检查
  • 定期导出审查防火墙规则集(nft list ruleset),清理已不再使用的放行规则

进阶排查技巧

使用 tcpdump 抓包分析连接失败原因

当常规工具无法定位问题时,抓包可以提供最底层的证据:

# 在服务端抓包(捕获目标端口的流量)
sudo tcpdump -i any port 8080 -n -v

# 输出示例:
# 10.0.0.10.54321 > 10.0.0.5.8080: Flags [S], ...  # 客户端发送 SYN
# 10.0.0.5.8080 > 10.0.0.10.54321: Flags [R.], ...  # 服务端返回 RST(Connection refused)
# 或:无任何响应(被 DROP)

# 在客户端抓包
sudo tcpdump -i any host 10.0.0.5 port 8080 -n

使用 ss 分析连接状态

# 查看所有 TCP 连接状态
ss -tan

# 统计各状态连接数
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn

# 查看特定端口的连接
ss -tanp | grep :8080

# 查看 SYN_RECV 状态(可能遭受 SYN flood 攻击)
ss -tan state syn-recv | wc -l

使用 nmap 扫描端口状态

# 扫描单个端口
nmap -p 8080 10.0.0.5

# 扫描端口范围
nmap -p 1-1024 10.0.0.5

# 扫描所有端口(耗时较长)
nmap -p- 10.0.0.5

# 输出解读:
# open    = 服务在监听
# closed  = 端口可达但无服务(收到 RST)
# filtered = 被防火墙过滤(无响应或 ICMP 不可达)

使用 curl 测试 HTTP 服务

# 测试 HTTP 连接
curl -v http://10.0.0.5:8080/

# 测试 HTTPS 连接(忽略证书错误)
curl -k -v https://10.0.0.5:8443/

# 测试 TCP 连接
curl -v --connect-timeout 5 telnet://10.0.0.5:8080

# 测试 DNS 解析
nslookup example.com
dig example.com

常见误区与陷阱

  • 不要混淆 Connection refused 与 Connection timed out:前者是主动拒绝(RST),后者是静默丢弃(DROP)
  • 不要忽略服务启动延迟:服务重启后需要几秒钟才能开始监听端口
  • 不要在防火墙中使用过于宽泛的规则:如 iptables -A INPUT -j ACCEPT 会开放所有端口
  • 不要忽略 Docker 的网络隔离:容器间默认不支持 DNS 服务发现,需要使用自定义网络
  • 不要在生产环境使用 telnet 调试:它会留下明文传输的凭证,使用 nc 或 ssh 替代

故障复现与验证清单

修复 connection refused 问题后,使用以下清单验证:

# 1. 验证服务监听状态
ss -tlnp | grep 端口号

# 2. 验证本机连接
curl http://127.0.0.1:端口号

# 3. 验证远程连接
nc -zv 目标IP 端口号

# 4. 验证防火墙规则
sudo nft list ruleset | grep 端口号

# 5. 验证服务日志无报错
journalctl -u 服务名 -n 10 --no-pager

最佳实践

  • 分层排查:先确认服务端口是否监听 → 再检查防火墙 → 最后检查网络路由和 DNS,逐层排除
  • 使用 REJECT 而非 DROP:在开发环境中,防火墙使用 REJECT 可以快速得到 Connection refused 反馈;生产环境根据安全需求选择
  • 端口扫描先行:使用 nmap -p 1-65535 目标IP 快速了解目标主机的端口开放情况,缩小排查范围
  • 日志驱动排查:查看 journalctl -u 服务名/var/log/syslog,通常能直接找到连接被拒的根本原因
  • 记录端口分配表:维护一份文档记录所有服务的端口分配,避免端口冲突和配置遗漏

延伸阅读

附录:网络排查命令速查表

任务命令说明
测试端口连通性nc -zv 目标IP 端口返回 Connection refused 或成功
查看监听端口ss -tlnp列出所有 TCP 监听端口
查看防火墙规则sudo nft list ruleset显示 nftables 规则
查看路由表ip route检查网络路由
测试 DNS 解析nslookup 域名检查 DNS 是否正常
跟踪路由traceroute 目标IP查看数据包路径
抓包分析sudo tcpdump -i any port 端口捕获网络流量
查看网络接口ip addr show检查 IP 地址配置

附录:Docker 网络排查命令

# 查看容器网络配置
docker inspect 容器名 --format '{{json .NetworkSettings}}'

# 查看容器 IP 地址
docker inspect 容器名 --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'

# 进入容器网络命名空间
docker exec -it 容器名 sh

# 在容器内测试连通性
docker exec 容器名 ping 目标IP
docker exec 容器名 curl http://目标:端口

# 查看 Docker 网络列表
docker network ls

# 查看网络详情
docker network inspect bridge

附录:防火墙规则管理速查

# nftables(现代系统推荐)
sudo nft list ruleset                          # 查看所有规则
sudo nft add rule inet filter input tcp dport 80 accept  # 放行 80 端口
sudo nft delete rule inet filter handle 3      # 删除规则

# UFW(Ubuntu)
sudo ufw status verbose                        # 查看状态
sudo ufw allow 80/tcp                          # 放行 80 端口
sudo ufw delete allow 80/tcp                   # 删除规则

# firewalld(CentOS/RHEL)
sudo firewall-cmd --list-all                   # 查看所有规则
sudo firewall-cmd --permanent --add-port=80/tcp  # 放行 80 端口
sudo firewall-cmd --reload                     # 重新加载

# iptables(传统)
sudo iptables -L -n -v                         # 查看规则
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT  # 放行 80 端口
sudo iptables -D INPUT 1                       # 删除第一条规则

案例 G:IPv6 与 IPv4 绑定地址不匹配导致连接失败

现象:Nginx 配置监听 [::]:80(IPv6),但客户端通过 IPv4 地址连接时被拒绝:

# 客户端连接
$ curl -v http://10.0.0.5:80
curl: (7) Failed to connect to 10.0.0.5 port 80: Connection refused

# 服务端检查
$ ss -tlnp | grep :80
LISTEN 0 128 [::]:80 [::]:* users:(("nginx",pid=1234,fd=6))

根因:Nginx 只绑定了 IPv6 地址 [::],没有绑定 IPv4 地址 0.0.0.0。在启用了 IPv4 的系统上,客户端通过 IPv4 连接时无法到达仅监听 IPv6 的服务。

修复:修改 Nginx 配置,同时监听 IPv4 和 IPv6:

# 修改 Nginx 配置
server {
    listen 80;           # 监听所有 IPv4 地址
    listen [::]:80;      # 同时监听 IPv6 地址
    ...
}

# 重启 Nginx
sudo nginx -t && sudo systemctl restart nginx

# 验证
$ ss -tlnp | grep :80
LISTEN 0 128 0.0.0.0:80 0.0.0.0:* users:(("nginx",...))
LISTEN 0 128 [::]:80 [::]:* users:(("nginx",...))

预防:服务配置中明确指定同时监听 IPv4 和 IPv6,或根据网络环境选择合适的监听地址。部署后用 ss -tlnp 确认绑定地址。

案例 H:systemd socket activation 导致服务端口未监听

现象:服务通过 systemd 管理,systemctl status 显示服务运行中,但端口无法连接:

# 服务状态正常
$ systemctl status myapp
● myapp.service - My Application
   Active: active (running) since ...

# 但端口未监听
$ ss -tlnp | grep :8080
# 无输出

# 检查 systemd socket 配置
$ cat /etc/systemd/system/myapp.socket
[Socket]
ListenStream=8080

根因:服务使用了 systemd socket activation,但服务进程没有正确接收 socket 文件描述符。systemd 会创建 socket 并监听端口,但服务进程需要通过 sd_listen_fds() API 接收。如果服务代码未实现此机制,socket 会处于 listening 状态但无法处理连接。

修复:检查服务是否正确实现了 socket activation,或改用直接端口绑定:

# 方案一:禁用 socket activation,让服务自行监听
sudo systemctl stop myapp.socket myapp
sudo systemctl disable myapp.socket
sudo systemctl start myapp

# 方案二:在服务代码中实现 socket activation
# 需要调用 sd_listen_fds() 接收 socket fd

预防:使用 systemd socket activation 前确认服务代码支持该机制。部署后用 ss -tlnp 确认端口状态。

↑ 回到顶部