ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Nginx 启动报错 Address already in use 的排查与解决指南

Nginx 启动报错 Address already in use 的排查与解决指南 说实话第一次看到nginx [emerg] bind() to 0.0.0.0:80 failed (98 Address already in use)这行日志大多数人都会有点懵。明明刚才nginx -t还显示 syntax ok配置文件也仔细检查过怎么一启动就报错而且这个 Address already in use 看起来特别像端口被谁占用了但ss一看 80 端口又没动静这种假性占用最让人头疼。这篇东西就是想把这个问题彻底讲透。我会先拆解这行报错的真实含义然后按出现概率从高到低梳理背后的原因再给你一套能直接照着敲的命令排查流程最后用一个真实复盘案例带你完整走一遍从报错到恢复的链路。不管你是刚上手 Linux 部署的小白还是已经在用 Nginx 跑反向代理的老手遇到这类启动失败都能快速定位、顺手解决。1. 从报错到定位这行 emergency 日志到底在说什么1.1 报错场景重现与错误文本拆解先还原一下典型场景你在服务器上写了一下午配置把 server_name、proxy_pass、SSL 证书路径全填好了nginx -t返回syntax is ok and test is successful信心满满执行nginx结果屏幕上直接甩出这么一段nginx: [emerg] bind() to 0.0.0.0:80 failed (98 Address already in use) nginx: [emerg] bind() to 0.0.0.0:80 failed (98 Address already in use) nginx: [emerg] still could not bind()注意一个细节第一行报错往往会重复出现两次最后还有一行still could not bind()。这其实是 Nginx 在尝试启动 worker 进程前的最后一个步骤——master 进程要把监听 socket 创建出来一旦失败就直接放弃启动所以叫[emerg]也就是 emergency 级别比 error 还严重属于Nginx 认为自己完全没法继续跑的那种错误。拆开看这行日志三个关键信息bind()这是操作系统提供的系统调用作用是把一个 socket 绑定到指定的 IP 地址和端口上。Nginx 的 master 进程启动时会为配置文件里的每个listen指令创建一个监听 socket然后调用bind()建立端口和 IP 的绑定关系。0.0.0.0:80这是 Nginx 默认的监听地址和端口。0.0.0.0表示监听本机所有 IPv4 网卡不只是某个具体内网 IP。也就是说无论请求从哪块网卡进来只要目标是 80 端口都由这个监听 socket 接收。(98 Address already in use)98 是 Linux 系统调用返回的错误码 errno对应EADDRINUSE翻译过来就是这个地址已经有人占用了。这是内核给你的明确答复不是 Nginx 自己的判断。1.2 为什么是 0.0.0.0:80 而不是你的公网 IP很多人会问我的服务器明明配了 IP为什么报错地址是0.0.0.0这要从 Nginx 的监听机制说起。你在配置文件里写的是listen 80;没有指定具体 IPNginx 就会帮你补全成0.0.0.0:80。如果你写的是listen 192.168.1.10:80;那报错就会变成具体的 IP 加端口。理解这一点很重要因为有时候你看到0.0.0.0:80被占用但ss查出来的监听记录确实是某个具体 IP。比如一个进程绑定了192.168.1.10:80另一个进程想绑0.0.0.0:80时同样会失败因为0.0.0.0:80的绑定范围会覆盖所有 IP和具体 IP 的监听产生冲突。反过来如果某个进程已经绑定了0.0.0.0:80你再想单独绑192.168.1.10:80也会失败这是同一个道理。所以在排查时不要只盯着:80看还要看监听地址是不是*:80或0.0.0.0:80只要是通配端口占用都会引发这个报错。2. 端口被占用的四大元凶按实际概率排了个序我把这些年帮人排查这个问题的经验按概率从高到低整理一下你先对号入座再动手操作。2.1 最常踩的坑机器上已经有一个 Nginx 在跑这个原因占了至少七成。典型情况是你忘了之前部署过 Nginx或者有别的项目在上面跑着自己又没注意。执行nginx启动新实例时新实例尝试绑定 80 端口发现旧的 master 进程已经把端口握在手里了自然就报 98。你可能会想不对啊我重启过服务器怎么还有旧进程问题往往出在两处用了 Docker 部署 Nginx但宿主机上也装了一个 Nginx两个都要监听 80通过systemctl restart nginx失败后又手动执行nginx此时可能残留了半死不活的旧进程。2.2 多个 Nginx 实例并存老的没退干净另一种常见情况你执行了kill -9 nginx-master-pid以为把 Nginx 杀干净了但 worker 进程是 Nginx master 的子进程master 被强杀后worker 不一定立刻退出尤其在处理长连接时可能继续存活一会儿。这些 worker 仍然持有着 80 端口的监听 socket导致你再次启动时报 98。还有一种是配置文件里写了多个listen而其中一个端口被其他 Nginx 实例占用。比如你想跑两个 Nginx 实例做测试A 实例监听 80B 实例也把 80 写进了配置B 启动时必报这个错。2.3 80 端口被其他 Web 服务占用Apache、Tomcat、Caddy、Python 的http.server、甚至某些监控面板都可能占用 80。最常见的是安装了宝塔、AMH 之类面板的环境面板自带的 nginx 和系统自带的 nginx 抢端口。另外有些 Java 应用的 Spring Boot 项目直接配置server.port80跑起来后也会占住 80。这类情况很容易被忽略因为你ps aux | grep nginx看不到任何 nginx 进程但端口确实是忙碌状态。2.4 TIME_WAIT 和假占用引发的误判这个稍微进阶一点。有时候ss -tlnp查 80 端口输出是干净的但 nginx 启动仍然报 98。这是因为系统存在大量处于TIME_WAIT或FIN_WAIT状态的 TCP 连接虽然这些连接已经不活跃了但内核的 socket 资源还在。正常情况下这些连接会在一段时间后自动清理但如果你的系统net.ipv4.tcp_tw_reuse没开加上端口被频繁短连接冲击就可能出现短时间的资源紧张。不过说实话这种情况在 Linux 上触发 98 的概率相对低更多是在高并发短连接场景下才会碰到。它虽然概率不高但排查起来最让人抓狂因为表面上看一切正常。3. 三分排查法netstat、ss、lsof、fuser 怎么组合用很多人上来就kill -9乱杀这是最危险的操作。正确思路是先确认谁占了端口再确认该不该杀最后才动手。3.1 第一步先看谁在监听 80 端口Linux 下查端口监听的命令有好几个我建议按顺序使用互为补充ss -tlnp | grep :80这个命令在大多数现代系统上都可用-t表示 TCP-l表示只显示监听中的 socket-n表示不解析服务名直接显示数字端口-p显示对应的进程信息。输出大概是LISTEN 0 128 0.0.0.0:80 0.0.0.0:* users:((nginx,pid12345,fd6))看到这里就清楚了是老 nginxpid 是 12345。如果没有输出再用 netstat 补一次netstat -tlnp | grep :80有些最小化安装的 Linux 没有 netstat需要先安装net-tools。ss是 iproute2 包里的一般都会带。如果 ss 和 netstat 都查不到但报错依然存在那就用 lsof 直接按端口反查lsof -i:80lsof 的强项是能看到动态建立的连接和套接字文件不只是监听中的连接。它还会把进程名、PID、用户都列出来信息很全。3.2 第二步区分 Nginx 进程和其他进程查到监听进程后先判断类型ps aux | grep nginx如果看到类似这样的输出root 12345 0.0 0.1 45228 1156 ? Ss 10:30 0:00 nginx: master process /usr/sbin/nginx www-data 12346 0.0 0.1 45632 1820 ? S 10:30 0:00 nginx: worker process说明确实有 nginx 在跑而且是 master worker 都齐全。这个时候直接考虑优雅停掉旧实例而不是 kill。如果ps aux | grep nginx没有输出但端口被占用那就是其他程序干的。继续看 lsof 的输出里的进程名是什么。3.3 第三步查看进程的启动时间和 PID 文件有些时候环境比较复杂光看进程名不够。你还得确认这个进程是不是你之前启动的防止误杀别人的服务。用ps -p PID -o lstart可以看进程的具体启动时间ps -p 12345 -o pid,lstart,cmd如果你的 Nginx 是用 nginx.org 的官方源装的PID 文件一般在/var/run/nginx.pid如果是自己编译安装可能在/usr/local/nginx/logs/nginx.pid或你指定的路径。直接查看这个文件cat /var/run/nginx.pid里面存的 PID 如果还是那个 12345说明文件没刷新旧进程还活着新进程自然抢不到端口。3.4 第四步确认本机防火墙是否也掺了一脚防火墙本身不会导致 bind() 98 错误但有一种特殊情况你在 iptables 里做了REDIRECT或DNAT规则把某个端口的流量转到 80同时 Nginx 也在监听 80这时候可能出现奇怪的兼容问题。另外SELinux 也会干扰端口绑定虽然报错通常不是 98但值得排查时顺手确认一下getenforce如果输出是Enforcing而 Nginx 绑定的是非标准端口SELinux 策略可能阻止。用ausearch -m avc -ts recent查看告警日志。不过话说回来80 端口是 SELinux 默认放行的除非你手动改了策略否则这个概率很小属于排查最后顺手看一眼的级别。4. 五种收尾方案选哪种取决于你的现场情况定位到原因之后解决方案就水到渠成了。但不同场景下最合适的动作不一样我按优先级给你排好。4.1 方案一用 Nginx 官方信号停止旧实例不要上来就 kill -9如果确认是旧 Nginx 占着端口官方推荐的做法是用信号控制。Nginx 支持两种停止信号nginx -s stop nginx -s quit区别是stop立即停止等价于发送 TERM 信号quit优雅停止先把正在处理的请求处理完再退出。生产环境你最好用quitnginx -s quit如果 Nginx 不是标准的启动路径需要指定前缀比如nginx -s quit -p /usr/local/nginx停完后再启动nginx这里要解释一下为什么不建议kill -9Nginx 的 worker 进程在处理请求时可能需要写日志、写入 upstream 连接状态等强杀容易留下不完整的连接也可能导致nginx.pid文件残留下次启动时又碰到奇怪问题。信号停止是 Nginx 自己处理收尾流程干净很多。4.2 方案二对确实非 Nginx 的占用进程做处理如果占用 80 的进程是 Apache、Tomcat 等你有两个选择要么停掉它要么让 Nginx 换端口。停 Apachesystemctl stop httpd # 或者 systemctl stop apache2永久禁用的话systemctl disable httpd如果是 Java 应用占用了 80找到 PID 后确认进程身份再kill或kill -9。注意对非 Nginx 的进程尤其是自己不太熟的进程先别急着杀用readlink -f /proc/PID/cwd看看进程的工作目录、cat /proc/PID/cmdline看完整启动命令确认不是数据库、监控等关键服务后再动手。4.3 方案三测试环境直接换监听端口如果你只是在本地测试或者跑的是临时服务最简单的方法就是让 Nginx 别用 80。改配置文件server { listen 8080; server_name localhost; ... }改完后重新加载nginx -t nginx -s reload为什么我推荐测试环境优先换端口而不是杀进程因为你在本地可能同时在跑多个项目每个都要 80 端口。学会用不同端口隔离服务才是正路。比如我一个机器上同时跑三个 Nginx 实例分别监听 8080、8081、8082互不干扰。这套思路在你后面做复杂网关、灰度发布时更有用。4.4 方案四处理 TIME_WAIT 和 socket 残留如果你排查后发现没有任何进程监听 80但 nginx 依然报 98那大概率是 TIME_WAIT 或半开连接的问题。先看看连接状态统计ss -tan | awk {print $1} | sort | uniq -c | sort -rn如果发现TIME_WAIT数量特别大可以先保守地开内核参数sysctl -w net.ipv4.tcp_tw_reuse1 sysctl -w net.ipv4.tcp_fin_timeout15tcp_tw_reuse的作用是允许内核在安全条件下复用 TIME_WAIT 状态的连接tcp_fin_timeout缩短 FIN 状态等待时间。这个操作对绝大多数场景没有负面影响但还是建议先确认系统版本支持在比较新的内核中tcp_tw_reuse默认就是开启的。还有一种残局之前强杀过 Nginx导致 socket 进入了CLOSE_WAIT但没有正常关闭。可以用这个命令查看具体 socketss -tanp | grep :80如果确认是残留 socket 但 PID 已经不存在等内核超时回收即可如果超时时间太长可以重启一下网络服务但这不是常规手段非必要不搞。4.5 方案五用 systemd 管理的机器上要注意的信号问题如果你是通过systemctl start nginx启动的而手动执行nginx报 98那要注意 systemd 可能已经启动了 nginx 但状态你看漏了。先看系统服务状态systemctl status nginx如果 active (running)说明 systemd 早就帮你把 Nginx 跑起来了你再手动执行nginx当然会撞端口。这种情况的处理方式是systemctl restart nginx手动启动的 Nginx 和 systemd 管理的是两套体系别混用。用 systemd 就全程用 systemd手动管理就全程手动混着来最容易出这种问题。5. 一次完整处理复盘从报错到恢复的全链路记录前面讲的是方法论这一节我带你走一遍完整的现场处理记录确保你以后遇到同类问题能照着复现整个排查链路。5.1 问题现象一台 Ubuntu 20.04 服务器上面跑着 Nginx FastDFS 做文件服务。某天我修改了 nginx.conf 里的 client_max_body_size然后执行nginx -s reload报错说 pid 文件找不到。我心想干脆重启吧就执行了systemctl restart nginx结果 restart 卡了很久最后 systemd 报超时接着手动执行nginx时出现了经典的 98 报错。眼看着服务彻底起不来了请求全部失败。5.2 排查操作和输出我先执行systemctl status nginx输出显示● nginx.service - A high performance web server Loaded: loaded (/lib/systemd/system/nginx.service; enabled) Active: failed (Result: exit-code)服务是 failed但原因没直接显示。接着查端口ss -tlnp | grep :80输出为空。奇怪80 端口没人监听怎么启动还是报 98再看所有监听状态ss -tanp | grep :80发现大量TIME_WAIT状态连接这就是假占用的现场。但是数量也不算特别夸张不到几百个按说不至于 bind 失败。我又去cat /run/nginx.pid发现文件里写着 PID 2048而ps -p 2048根本查不到进程。PID 文件残留 systemd 管理状态混乱两边叠加。5.3 根因修复我先清理 PID 文件rm -f /run/nginx.pid然后用 systemd 彻底重置服务状态systemctl daemon-reload systemctl reset-failed nginx systemctl start nginx这次启动成功了。事后分析真正的问题是之前 reload 失败后systemd 的 nginx service 里还残留了旧的 RuntimeDirectory 状态PID 文件路径不一致导致 Nginx 误判。5.4 验证和后续预防启动后我做了三个验证动作curl -I http://127.0.0.1 systemctl status nginx ss -tlnp | grep :80确认 HTTP 返回 200、服务 active、端口监听正常。然后我还加了预防措施以后在所有用到 Nginx 的机器上统一用 systemd 管理不再手动执行nginx命令启动手动调试时也只用nginx -t验证配置启动和停止一律交给systemctl。6. 从这次事故里提炼的Nginx 启动前检查清单排查完一次问题我习惯把它沉淀成一套固定动作以后每次启动 Nginx 前只需要 30 秒过一遍就能避免绝大多数 98 报错。6.1 每次启动前必看的三个命令第一个命令查端口第二个命令查进程第三个命令查 PID 文件三连击覆盖 99% 的问题ss -tlnp | grep :80 ps aux | grep nginx cat /run/nginx.pid 2/dev/null端口有监听、进程存在、PID 文件有值——那 Nginx 大概率本来就在跑你要做的是 reload 而不是 start。端口没监听但进程有 nginx ——多半是进程卡在异常状态先处理进程。端口没监听、进程没有、但 PID 文件存在——清理 PID 文件再启动。这套三连击我用了很多年包括在排查nginx 替换 SSL 证书不生效这类问题时也很有用。很多证书不生效的根源就是你以为 reload 了其实 reload 根本没执行成功旧配置还在跑。6.2 nginx -t 真的通过但启动仍报错查这个大坑nginx -t通过只代表配置文件语法没问题不代表端口一定能绑定成功。语法检查和启动检查是两件事语法检查不触碰网络资源启动时才会真正调用bind()。所以那晚我死在迷信 nginx -t上。配置文件写得再正确只要 80 端口已经被占nginx -t照样显示 syntax is ok然后启动阶段再突然给你一记 98。以后别把nginx -t当启动成功预测器它只能验证语法层面。另外一个容易踩的坑是多套配置混在一起。比如你写了主配置和二级域名配置里面有两个 server 块都监听了 80 端口但其中一个被 included 了两次等于要在同一端口绑定两次。nginx -t可能不报这个错启动时才报。排查时可以用nginx -T查看 Nginx 实际加载的全部配置内容确认没有重复监听nginx -T | grep -E listen|server_name | head -406.3 用 systemd 管理的机器上要注意的信号问题如果你用systemctl start nginx启动而 Nginx 是 yum/apt 装的systemd 服务文件里默认包含了PIDFile/run/nginx.pid。这个设计很巧妙systemd 依赖 PID 文件来识别主进程。但问题来了如果你之前手动用nginx -s stop停止过 NginxPID 文件会被正确移除但如果你用kill -9强杀PID 文件可能还在systemd 再次启动时就会读到旧 PID 文件导致状态判断异常甚至出现systemd 认为 Nginx 在跑实际没跑的假象。这类场景下的处理顺序应该是先systemctl daemon-reload重新加载服务定义再检查 PID 文件是否存在且有效如果 PID 文件无效删掉再systemctl start实在不行执行pkill nginx清场后重新启动。如果你用的 Nginx 是编译安装的没接入 systemd而是自己写了个 init 脚本或 service 文件记得确认服务文件里 ExecStart 和 ExecReload 的路径跟你实际安装路径完全一致。很多奇怪的启动失败都源于 systemd 服务文件写的是/usr/sbin/nginx但你装到了/usr/local/nginx/sbin/nginx。最后再分享一个和这个报错纠缠多次后提炼的经验遇到 bind 失败别慌按端口 → 进程 → PID 文件 → 连接状态的顺序查八成问题五分钟内就能定位。真正耽误时间的不是排查本身而是不敢杀、生怕误杀的犹豫心理。只要确认了占用方的身份该停的停、该换端口的换端口Nginx 本身的机制并不复杂复杂的是环境里各种服务纠缠在一起的状态。把上面这套清单存下来下次再看到[emerg]时你就能从怎么会这样直接跳到我知道是谁在捣乱了。
返回列表