Linux 排查:ss -tlnp 与端口占用的三种典型场景
Linux 排查:ss -tlnp 与端口占用的三种典型场景
端口问题大概是运维日常里最高频的一类:服务起不来、起了连不上、连上了越跑越慢。这三种现象看着都像"端口有问题",但根因完全不同,用同一条命令 ss -tlnp 就能分出方向。
先把参数拆开:-t TCP、-l 只看监听、-n 不解析服务名和域名、-p 显示进程。四个字母组合起来,就是"谁在监听哪个 TCP 端口"这一个问题的最短答案。
$ ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=812,fd=3))
LISTEN 0 511 127.0.0.1:8080 0.0.0.0:* users:(("java",pid=20431,fd=87))
LISTEN 0 128 [::]:443 [::]:* users:(("nginx",pid=1203,fd=6))
场景一:服务起不来,报 bind: Address already in use
这是最好处理的一种,因为有明确报错。
$ ./app
listen tcp :8080: bind: address already in use
第一步确认占用者:
$ ss -tlnp | grep :8080
LISTEN 0 511 127.0.0.1:8080 0.0.0.0:* users:(("java",pid=20431,fd=87))
这里有个很容易踩的坑:-p 能不能显示进程,取决于你的权限。 用普通用户跑,占端口的又是别人的进程,Process 列会直接是空的,只留一个 users:(("java",pid=...)) 都没有的空行。这时候别怀疑命令坏了,加 sudo 再跑一次:
$ sudo ss -tlnp | grep :8080
如果连 ss 都没有(精简镜像里常见),有两个等价替代:
$ sudo lsof -i:8080 -sTCP:LISTEN
$ sudo fuser -n tcp 8080
拿到 pid 之后,先看清楚它是什么再动手:
$ ps -o pid,ppid,user,cmd -p 20431
PID PPID USER CMD
20431 1 app java -jar /opt/app/gateway.jar --server.port=8080
结论: 这一步的产出不是"杀掉进程",而是"这个端口到底该不该归它"。我见过不止一次,真正的故障是某个历史遗留的 systemd 服务还在开机自启,把端口占了,重启业务当然起不来——systemctl is-enabled 比 kill -9 有用。
场景二:端口在监听,但外部连不上
这种最费时间,因为本机 curl 是通的,很容易先去怀疑防火墙和安全组。其实 ss 输出里的 Local Address 已经把答案写在那儿了。
对比两行:
LISTEN 0 511 127.0.0.1:8080 0.0.0.0:* users:(("java",pid=20431,fd=87))
LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* users:(("java",pid=20431,fd=87))
127.0.0.1:8080—— 只绑在回环网卡上,只有本机能连。别的机器、甚至同宿主机上的另一个容器都连不进来。0.0.0.0:8080—— 所有 IPv4 网卡都监听。[::]:8080和[::1]:8080—— 是 IPv6 版本,同样有"只回环"和"全部"的区别。
所以"本机通、外部不通"的第一步不是查安全组,是确认绑定地址:
$ curl -sI http://127.0.0.1:8080/ | head -1
HTTP/1.1 200 OK
$ curl -sI --connect-timeout 3 http://10.0.0.12:8080/ | head -1
curl: (7) Failed to connect to 10.0.0.12 port 8080: Connection refused
本机 200、内网 IP 拒绝,那就是绑错了。改配置重绑 0.0.0.0 即可。确认这一步之后再去查安全组,能省掉一大半误判。
顺便记住 Connection refused 和超时的区别:
- refused —— 包到了机器,但没有进程在这个端口上监听(或被本机防火墙 reject)。方向是"服务/绑定"。
- 超时 —— 包根本没回来。方向才是"安全组、路由、对端没开"。
场景三:连接数跑满、TIME_WAIT 一堆
前两种情况是"起不来",这种是"跑着跑着不行了"。先看总量:
$ ss -s
Total: 412
TCP: 389 (estab 42, closed 301, orphaned 0, timewait 287)
再按状态拆:
$ ss -tan state time-wait | wc -l
287
$ ss -tan state established | awk '{print $4}' | cut -d: -f1 | sort | uniq -c | sort -rn | head
38 10.0.0.12
这里我要泼一盆冷水:TIME_WAIT 是 TCP 的正常收尾状态,不是故障。 主动关闭连接的一方必然进入 TIME_WAIT,持续 2MSL(Linux 上 60 秒)。287 个 TIME_WAIT 在一台正常跑业务的小机器上完全不算多。
真正要看的指标是 closed 涨得快不快,以及 estab 有没有顶到上限:
$ ss -tan state established | wc -l
$ ss -tan state syn-recv | wc -l # 有人疯狂建连但握不上手
如果 closed 很大、estab 却很小,说明业务在疯狂建短连接——典型场景是每次请求都新建 HTTP 连接、或者数据库连接没走池子。正确解法是加连接池 / 复用连接,不是去调 net.ipv4.tcp_tw_reuse 或 tcp_max_tw_buckets。 后两个参数在 NAT 环境里还可能引入更隐蔽的问题,属于治标不治本。
一个容易翻车的细节:在容器里看不到宿主机进程
这个坑我踩过。在容器里跑 ss -tlnp,宿主机上明明有进程占了 8080,容器里却完全看不到——因为容器有自己的 network namespace,ss 只列当前 namespace 的 socket。
要看宿主机的,得借宿主机的 namespace:
$ nsenter -t <宿主机上某进程pid> -n ss -tlnp
或者直接从宿主机侧查,再对应到容器:
$ sudo ss -tlnp | grep :8080
LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* users:(("docker-proxy",pid=30122,fd=4))
$ docker ps --filter publish=8080
看到 docker-proxy 就说明端口是被容器映射走的,去查容器而不是查进程树。
三种场景的排查顺序
| 现象 | 先看什么 | 常见根因 |
|---|---|---|
bind: Address already in use |
sudo ss -tlnp | grep :端口 |
历史服务残留、开机自启、上一个实例没退干净 |
| 本机能连、外部连不上 | ss -tlnp 的 Local Address |
绑了 127.0.0.1;确认后才查安全组 |
| 越跑越慢、建连失败 | ss -s 与 ss -tan state xxx |
短连接过多、连接池没配好、SYN 队列满 |
一句话总结:ss -tlnp 回答"谁在听",Local Address 回答"谁能连",ss -s 与状态过滤回答"为什么连不上"。 把这三件事分开看,端口类的故障基本就不会走弯路。
- 点赞
- 收藏
- 关注作者
评论(0)