Linux服务器如何查看端口占用?ss、lsof与netstat命令详解

线上发布最尴尬的一幕,莫过于执行 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 可以直接杀掉它(谨慎使用)。

五、三个工具怎么选:对比与速查

工具所属软件包数据来源优势劣势推荐场景
ssiproute2内核 netlink 直读速度快、支持过滤表达式、新系统自带输出列紧凑,新手不易读日常首选,高并发机器必用
lsoflsof遍历 /proc语法直观、可按进程/文件维度查进程多时较慢,常需手动安装按端口反查进程、查 deleted 文件
netstatnet-tools解析 /proc/net/*输出清晰、兼容老系统、能看路由与统计已停止维护、连接多时卡顿老系统维护、看路由表和协议统计
fuserpsmisc/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(确认后再执行)

六、实战排查步骤清单

遇到"端口起不来 / 端口访问不通",按以下顺序走,基本不会漏:

  1. 确认报错原文:执行 systemctl status 服务名 -l 或 journalctl -u 服务名 -n 50,看是 Address already in use 还是 Permission denied(1024 以下端口需要 root 或 CAP_NET_BIND_SERVICE)。
  2. 查端口归属:ss -lntp sport = :端口,如果无输出说明没有进程监听,问题在服务端配置而非端口冲突。
  3. 确认监听地址:重点看 Local Address 是 0.0.0.0:端口 还是 127.0.0.1:端口。后者只能本机访问,需要改配置里的 bind / listen 指令。
  4. 确认进程身份:lsof -nP -iTCP:端口 -sTCP:LISTEN 拿到 PID,再 ps -fp PID 看启动命令,判断是不是预期进程(常见误判:被另一个测试版服务抢先占用)。
  5. 本机自测:curl -v 127.0.0.1:端口 能通说明服务正常,问题在防火墙或安全组;不通说明服务本身有问题。
  6. 查防火墙:firewall-cmd --list-ports(firewalld)或 iptables -L -n(iptables),云服务器还要检查厂商安全组规则。
  7. 处理冲突:确认无用进程后 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:该参数在新内核已移除,写进文档会误导他人。