线上发布最尴尬的一幕,莫过于执行 systemctl start nginx 后终端甩出一句 "Address already in use"(地址已被占用),服务起不来,页面 502,而你根本不知道这个端口被谁占着。更常见的情况是:安全组已经放行了 8080,本机 curl 也能通,但外网就是访问不了——这时候你要确认的其实不是"通不通",而是"这个端口到底有没有进程在 LISTEN 状态监听,监听在哪个网卡地址上"。查看端口占用,是 Linux 服务器排障里频率最高的动作之一,掌握 ss、lsof、netstat 三件套的组合用法,基本可以在 30 秒内给出结论。
一、先厘清概念:什么叫"端口被占用"
很多人一说端口占用就只想到"有程序在监听",实际上排查时要分三种情形,判断依据完全不同:
- 端口已被 LISTEN 监听:最常见。某个进程绑定了该端口并等待连接,此时再启动第二个进程绑定同一端口会直接报 EADDRINUSE。
- 端口处于 TIME_WAIT / CLOSE_WAIT:不是被"占用",而是连接尚未完全释放。TIME_WAIT 大量堆积通常出现在高并发短连接场景(如代理、爬虫),可以通过
ss -ant | grep TIME-WAIT | wc -l统计数量。 - 端口被 ESTABLISHED 连接使用:本机作为客户端对外建连时也会占用一个本地临时端口,范围由
/proc/sys/net/ipv4/ip_local_port_range控制(默认通常是 32768 60999)。
还有一种容易忽略的情况:监听地址不匹配。进程只监听 127.0.0.1:8080,那么外网无论如何都连不上,但你会觉得"8080 明明被占用了"。所以查看端口占用时,必须同时看 Local Address 这一列的 IP 部分,而不只看端口号。
# 查看本机可用本地端口范围
cat /proc/sys/net/ipv4/ip_local_port_range
# 统计各 TCP 状态连接数,快速判断 TIME_WAIT 是否堆积
ss -ant | awk 'NR>1{print $1}' | sort | uniq -c | sort -rn
上面第二条命令的输出里,如果 TIME-WAIT 达到几万条,说明短连接回收压力大,可以考虑开启 net.ipv4.tcp_tw_reuse(注意:tcp_tw_recycle 在 Linux 4.12 之后已被移除,不要再写进调优文档)。
二、ss:现在的主力工具,快且准
ss(socket statistics)来自 iproute2 软件包,它直接从内核获取信息,而不是像 netstat 那样去解析 /proc/net/tcp 文本文件。在连接数上万台机器上的速度差距非常明显:netstat 可能要卡十几秒,ss 基本秒回。新装的最小化系统(CentOS 8 / Rocky / Ubuntu 20.04+)默认只有 ss,没有 netstat,所以 ss 应该是你的第一选择。
2.1 最常用的组合:ss -lntp
# 查看所有处于监听状态的 TCP 端口,显示进程名和 PID,不做域名/服务名解析 ss -lntp # 同时看 TCP 和 UDP ss -lntup # 只看某个端口 ss -lntp sport = :80 ss -lntp '( sport = :8080 or sport = :443 )' # 查看所有已建立的连接(含进程) ss -antp | grep ESTAB
参数逐位拆解,便于记忆:-l 表示只显示 listening 套接字;-n 表示不做解析,直接以数字显示端口(不加的话 80 会显示成 http,慢且不利于 grep);-t 表示 TCP;-u 表示 UDP;-p 表示显示占用该套接字的进程信息。注意 -p 需要 root 权限才能看到别人的进程,普通用户执行会显示为空。
2.2 怎么读 ss 的输出
典型一行输出如下:
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1234,fd=6))
从左到右各列含义是:State 连接状态(LISTEN/ESTAB/TIME-WAIT);Recv-Q 接收队列当前字节数;Send-Q 发送队列;第三、四列是 Local Address:Port 与 Peer Address:Port。最关键的是 Send-Q 在 LISTEN 状态下的含义:它表示全连接队列(accept queue)的最大长度,511 这个值通常来自 Nginx 配置里的 backlog 参数与内核 somaxconn(默认 128,很多镜像会调到 511 或 1024)取较小者。如果这个值很小而并发又高,可能出现连接被丢弃的情况。最后一段 users 直接给出了进程名和 PID,排障时拿到 PID 就可以 kill 或进一步 ls -l /proc/PID/cwd 看它跑在哪个目录。
2.3 用过滤表达式精确定位
ss 支持一套过滤语法,比 grep 更严谨,不会因为 8080 命中 18080 而误判:
# 按目标端口过滤(注意 dport 是远端,本机对外连接用得多) ss -ant '( dport = :443 or sport = :443 )' # 按状态过滤 ss -ant state established ss -ant state time-wait # 按 IP 过滤 ss -ant dst 10.0.0.5 # 只看某个进程打开的套接字 ss -antp | grep nginx
三、lsof:从端口号反查进程,最直观
lsof(list open files)的设计初衷是列出系统打开的文件,而在 Linux 下"一切皆文件",网络套接字也算文件,所以它天然可以用来查端口。lsof 的最大优势是语法最符合直觉——你想知道 8080 是谁在用,就直接 lsof -i:8080。
# 查看 8080 端口被谁占用(TCP+UDP) lsof -i:8080 # 只看 TCP,且处于 LISTEN 状态 lsof -iTCP:8080 -sTCP:LISTEN # 不解析主机名和端口名,输出更快更干净 lsof -nP -iTCP -sTCP:LISTEN # 查看某个进程打开的所有网络连接 lsof -p 1234 lsof -c nginx # 反向:列出某进程打开的普通文件 lsof -p 1234 | grep -v mem
典型输出:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME nginx 1234 root 6u IPv4 25671 0t0 TCP *:http (LISTEN) nginx 1235 www 6u IPv4 25671 0t0 TCP *:http (LISTEN)
解读要点:COMMAND/PID/USER 直接告诉你进程名、进程号、运行用户;FD 是文件描述符,6u 中的 u 表示读写模式;TYPE 为 IPv4/IPv6;NAME 列是重点,*:http 表示监听所有地址的 80 端口(因为没加 -P 所以显示服务名),如果这里显示 localhost:http 就说明只监听本机,外网访问不通的原因就找到了。
需要注意:最小化安装的机器 lsof 常常没装,用 yum install -y lsof 或 apt install -y lsof 补上。另外 lsof 遍历的是 /proc,在进程数极多(数千)的机器上会比 ss 慢,所以优先 ss,需要按进程维度细看时用 lsof。
还有一个杀手锏场景:文件被删但空间没释放。日志被 rm 掉之后磁盘空间仍未回收,就是因为有进程还持有它的句柄,用 lsof | grep deleted 可以找出来,重启对应进程即可释放。
四、netstat:老工具,仍然值得会
netstat 来自 net-tools 软件包,属于"传统三件套"(ifconfig、netstat、route)之一,官方已不再维护,CentOS 7 之后默认不再安装。但它的优势是几乎所有发行版和几乎所有老工程师都认识它,输出格式也最容易被各种文档引用,所以还是要会看。
# 查看所有监听端口 netstat -lntp # 查看所有连接(含监听与非监听),并显示进程 netstat -anp # 只过滤目标端口 netstat -anp | grep :3306 # 查看路由表(netstat 的拿手好戏) netstat -rn # 查看各协议统计信息,排查丢包重传 netstat -s
参数含义:-a 显示所有套接字(默认不显示 LISTEN 之外的);-n 数字显示;-t TCP;-u UDP;-l 仅监听;-p 显示进程;-r 路由表;-s 统计。如果执行提示 command not found,用 yum install -y net-tools 或 apt install -y net-tools 安装。
netstat 输出里 State 列是单独一列,读起来比 ss 清晰;但它在海量连接下会明显变慢,且 netstat -anp | grep :80 这种写法容易误命中 8080、18080,建议写成 grep ':80 ' 或用 awk 精确匹配列。此外还有个更轻量的替代:fuser -n tcp 80 直接返回占用该 TCP 端口的 PID,fuser -k -n tcp 80 可以直接杀掉它(谨慎使用)。
五、三个工具怎么选:对比与速查
| 工具 | 所属软件包 | 数据来源 | 优势 | 劣势 | 推荐场景 |
|---|---|---|---|---|---|
| ss | iproute2 | 内核 netlink 直读 | 速度快、支持过滤表达式、新系统自带 | 输出列紧凑,新手不易读 | 日常首选,高并发机器必用 |
| lsof | lsof | 遍历 /proc | 语法直观、可按进程/文件维度查 | 进程多时较慢,常需手动安装 | 按端口反查进程、查 deleted 文件 |
| netstat | net-tools | 解析 /proc/net/* | 输出清晰、兼容老系统、能看路由与统计 | 已停止维护、连接多时卡顿 | 老系统维护、看路由表和协议统计 |
| fuser | psmisc | /proc | 一条命令返回 PID,可直接 -k 杀进程 | 信息量少 | 确认端口归属后要立即处理 |
常用命令速查
| 需求 | 命令 |
|---|---|
| 看所有监听端口及进程 | ss -lntp |
| 查 8080 是谁占用 | ss -lntp sport = :8080 或 lsof -nP -iTCP:8080 -sTCP:LISTEN |
| 看已建立连接 | ss -antp | grep ESTAB |
| 统计 TIME_WAIT 数量 | ss -ant | grep TIME-WAIT | wc -l |
| 看被删除但未释放的文件 | lsof | grep deleted |
| 看路由表 | netstat -rn 或 ip route |
| 杀掉占用端口的进程 | fuser -k -n tcp 8080(确认后再执行) |
六、实战排查步骤清单
遇到"端口起不来 / 端口访问不通",按以下顺序走,基本不会漏:
- 确认报错原文:执行
systemctl status 服务名 -l或journalctl -u 服务名 -n 50,看是 Address already in use 还是 Permission denied(1024 以下端口需要 root 或 CAP_NET_BIND_SERVICE)。 - 查端口归属:
ss -lntp sport = :端口,如果无输出说明没有进程监听,问题在服务端配置而非端口冲突。 - 确认监听地址:重点看 Local Address 是 0.0.0.0:端口 还是 127.0.0.1:端口。后者只能本机访问,需要改配置里的 bind / listen 指令。
- 确认进程身份:
lsof -nP -iTCP:端口 -sTCP:LISTEN拿到 PID,再ps -fp PID看启动命令,判断是不是预期进程(常见误判:被另一个测试版服务抢先占用)。 - 本机自测:
curl -v 127.0.0.1:端口能通说明服务正常,问题在防火墙或安全组;不通说明服务本身有问题。 - 查防火墙:
firewall-cmd --list-ports(firewalld)或iptables -L -n(iptables),云服务器还要检查厂商安全组规则。 - 处理冲突:确认无用进程后
kill PID,或改用其他端口;不要直接kill -9,先给进程优雅退出的机会。
小结
查看端口占用这件事,工具本身不难,难的是分清"监听""已连接""只监听本机"这三种状态,以及拿到 PID 之后能继续往下追(进程是谁、跑在哪个目录、该不该杀)。日常建议以 ss -lntp 打底,遇到需要按进程或文件维度细查时上 lsof,维护老系统时再祭出 netstat。把这三条命令练成肌肉记忆,服务起不来的故障定位时间可以从十几分钟压到一分钟以内。
避坑清单
- 不加 sudo 看不到进程名:ss -p / netstat -p 必须以 root 执行,否则 users 列为空,容易误判"端口没人占"。
- grep 端口要防误命中:
grep :80会命中 8080、18080,用grep ':80 '或 ss 的 sport 过滤表达式更稳妥。 - Listen 状态的 Send-Q 不是当前排队数,而是全连接队列上限,别当成积压量读。
- UDP 没有 LISTEN 状态,查 UDP 端口用
ss -lnup,看到的是绑定而非监听。 - 容器环境要进容器内部查:宿主机上
ss -lntp看到的是宿主机命名空间,Docker 映射的端口在宿主机上通常表现为 docker-proxy 进程。 - 别轻信 tcp_tw_recycle:该参数在新内核已移除,写进文档会误导他人。
企业QQ咨询




