sing-box macOS 报 address already in use?先查端口、进程和 TIME_WAIT
简答:先不要反复重启或直接 kill 所有网络进程:从错误日志读取具体地址和端口,用 lsof 检查是否仍有监听者;如果 lsof(含 sudo)为空但 netstat 显示 TIME_WAIT 或 FIN_WAIT_2,尤其是刚从 root 切换到普通用户,优先等待连接状态清理或临时换端口。本文检查日为 2026 年 10 月 22 日,相关公开案例 issue #4650 仍显示为 Open,不能把它当作已修复公告。
定义:address already in use 是操作系统在 sing-box 尝试绑定某个监听地址和端口时返回的错误,表示该地址端口仍被活动监听者、其他实例、不同地址族的绑定或连接状态占用。
先看这张判断卡:不要把所有端口错误都当成配置损坏
如果日志类似下面这样,真正需要排查的是 127.0.0.1:2080 这一组地址和端口,而不是先重装配置:
FATAL start inbound: listen tcp 127.0.0.1:2080: bind: address already in use| 检查结果 | 更可能的原因 | 下一步 |
|---|---|---|
lsof 有输出,且 COMMAND 是 sing-box | 旧实例、重复启动,或图形客户端与命令行实例同时运行 | 先确认 PID 和 USER,只停止确认属于 sing-box 的实例 |
lsof 有输出,但 COMMAND 是其他程序 | 其他代理软件、开发服务或系统服务占用端口 | 停止已确认的冲突服务,或修改 sing-box 的 listen_port |
| 普通用户 lsof 无输出,sudo lsof 也无输出;netstat 有 TIME_WAIT/FIN_WAIT_2 | 进程已退出,但连接状态仍存在 | 等待状态清理,或暂时换一个端口验证配置 |
| 刚用 sudo 或 root LaunchDaemon 运行,随后改用普通用户 | 与公开 issue #4650 描述相似的跨用户重绑定现象 | 使用同一用户身份重启,或等待后再试;不要据此断言所有 macOS 都必然复现 |
| 日志是 UDP,检查 TCP 没结果 | 检查协议不匹配 | 改用 UDP 查询;UDP 没有 TCP 的 TIME_WAIT 状态 |
sing-box 的入站由 type、listen 和 listen_port 等字段组成,具体类型和字段应以sing-box 入站文档为准。不要把不存在的“DNS 入站类型”或未经配置的默认端口写进判断结论。
第一步:从日志确认冲突的是哪个入站或 API
先复制第一条包含 bind: 的错误,不要只看最后一行。重点记录三项:
- 协议:
tcp还是udp。 - 监听地址:例如
127.0.0.1、::或0.0.0.0。 - 端口:例如
2080,它决定后续命令查询什么。
如果日志带有 inbound/mixed、inbound/socks 或 inbound/http,可以直接回到对应 inbounds[] 项检查。若错误来自 external_controller,则检查 experimental.clash_api.external_controller。Clash API 的文档明确说明,external_controller 为空时不会启用 API,因此没有配置该字段时,不应把 9090 等端口当作必然存在的冲突来源;详见sing-box Clash API 配置文档。
127.0.0.1:2080、:::2080 和 0.0.0.0:2080 不应简单当成三个互不相关的端口。IPv4/IPv6 通配地址可能覆盖更广的本地接口,导致你以为只检查了 localhost,实际仍与另一监听发生冲突;排查时应把日志中的地址、协议和端口原样带入命令。
如果是 tun 入站,重点不在某个 TCP listen_port。TUN 通过虚拟接口工作,不应因为看到 TUN 就假设它占用了 mixed 入站的监听端口;仍要以实际日志和配置字段为准。
第二步:用 macOS 命令区分活跃监听、残留连接和 UDP 冲突
下面命令只用于 macOS 本机只读检查。TCP 和 UDP 要分开查,命令中的 <PORT> 替换为日志里的纯数字端口。
# macOS;无需 sudo;TCP;输出 COMMAND、PID、USER 和 LISTEN 地址
lsof -nP -iTCP:<PORT> -sTCP:LISTEN
# macOS;可能需要 sudo 才能看到其他用户或 root 的进程;TCP;查看监听者
sudo lsof -nP -iTCP:<PORT> -sTCP:LISTEN
# macOS;无需 sudo;TCP;只看指定端口的 TIME_WAIT
netstat -anv -p tcp | grep '\.<PORT> '
# macOS;无需 sudo;TCP;同时关注 TIME_WAIT 和 FIN_WAIT_2
netstat -anv -p tcp | egrep 'TIME_WAIT|FIN_WAIT_2' | grep '\.<PORT> '
# macOS;无需 sudo;UDP;查看指定端口是否有活动占用
lsof -nP -iUDP:<PORT>
# macOS;无需 sudo;UDP;按端口末段筛选,避免把 TCP 结果混进来
netstat -anv -p udp | grep '\.<PORT> '看结果时,COMMAND 是进程名,PID 是进程号,USER 用来判断是否由 root 或当前用户运行,NAME 用来核对实际监听地址。使用 grep '\.<PORT> ' 是为了避免端口 2080 误匹配 12080 或 20801。
如果 TCP 的 lsof 仍显示进程,说明是活跃监听,不能靠等待解决。先确认它确实是 sing-box,再处理 PID。若 lsof 为空而 netstat 显示 TIME_WAIT 或 FIN_WAIT_2,说明检查重点已经从“谁在监听”转为“连接状态何时清理”。
安全停止单个已确认的 sing-box PID:
# macOS;通常需要 sudo 才能停止 root 所属 PID;TCP/UDP 均适用
# 先把 <PID> 换成 lsof 输出中确认属于 sing-box 的进程号
kill -TERM <PID>
# 如果 PID 属于 root,再使用管理员权限
sudo kill -TERM <PID>不要直接执行按端口批量杀进程的命令,也不要把不认识的 COMMAND 当成 sing-box。kill -TERM 只是发送正常终止请求;发送后重新运行 lsof 确认监听是否消失,再决定是否启动。若终端前台运行的是 sing-box run,优先在该终端按 Control-C。
第三步:判断是普通端口冲突,还是 root 切换后的短暂重绑定
公开 issue #4650 于 2026 年 10 月 20 日提交,报告环境为 macOS 27.0.1 和 sing-box 1.14.3。报告者描述的复现方式是:先以 root 运行并接受连接,再停止 root 实例,立即以普通用户绑定相同端口;其测试中约 30 秒后恢复,客户端连接保持打开时最长约 60 秒。issue 在本文检查日仍显示为 Open,所以上述内容应理解为一个公开复现案例,而不是所有 macOS 版本都适用的官方定论。详情可查看sing-box issue #4650。
| 场景 | 如何核对 | 建议处理 |
|---|---|---|
| 仍有活跃监听 | lsof 显示 sing-box 或其他 COMMAND,状态为 LISTEN | 先停止对应实例;如果是其他服务,改用不同端口或按其文档停止 |
| 跨用户重启 | 刚执行过 sudo/root 启动,随后改普通用户;lsof 可能为空,netstat 显示连接状态 | 尽量使用同一用户身份管理生命周期;等待后重试,或先改用临时端口 |
| TIME_WAIT | netstat 显示 TIME_WAIT | 等待,不要反复启动;也可以用临时端口验证配置是否正常 |
| FIN_WAIT_2 | netstat 显示 FIN_WAIT_2 | 等待客户端关闭连接;若连接持续存在,改端口比反复重启更稳妥 |
| 同端口多地址绑定 | 配置中多个入站,或通配地址与 localhost 使用同一端口 | 为每个实际监听入口分配唯一组合,并重新检查所有 listen 与 listen_port |
如果你只想验证是否是端口状态造成的,不必立即改整个配置。复制当前配置到测试文件,把对应入站的 listen_port 临时改为一个未占用端口,再运行配置检查。若换端口可以启动,说明问题集中在原地址端口组合;若换端口仍失败,则继续看日志中的下一条错误。
reuse_addr 是 sing-box 监听字段之一,官方文档标注其自 1.12.0 起提供,用于复用监听地址;但 issue #4650 描述的是 root 与非 root 之间的 Darwin 重绑定现象,不能据此承诺它一定解决跨用户场景。若你的当前版本和入站类型支持该字段,可以把它作为测试项,而不是永久修复保证;无效时仍按等待、统一运行用户或更换端口处理。字段说明见sing-box Listen Fields 文档。
第四步:SFM、命令行和 LaunchDaemon 要分开处理
| 运行方式 | 先做什么 | 再检查什么 |
|---|---|---|
| SFM 图形客户端 | 在应用内停止服务或断开连接,再退出或重新启动应用 | 确认没有同时运行命令行 sing-box;然后查看应用的服务日志 |
| 命令行 sing-box | 在运行命令的终端按 Control-C | 用 lsof 确认监听消失,再重新运行命令 |
| 系统级 LaunchDaemon | 先识别 plist 的 label,再用 bootout 卸载服务 | 确认 system 域不再有对应进程,之后再 bootstrap 或启动 |
| 用户级 LaunchAgent | 不加 sudo 检查当前用户域 | 确认 gui/当前 UID 域没有自动重启旧实例 |
Apple 平台客户端通过 NetworkExtension 运行服务和 TUN,官方特性页也说明服务日志入口为 Settings → View Service Log;因此,图形客户端优先从应用内停止服务、查看服务日志,不要把它简单等同于一个前台命令行进程。相关说明见sing-box Apple 平台特性文档。
如果怀疑是 launchd 自动拉起,先只读列出服务和启动文件:
# macOS;无需 sudo;查看 system 域中名称含 sing 的服务
sudo launchctl list | grep -i sing
# macOS;无需 sudo;查看当前用户域的服务
launchctl list | grep -i sing
# macOS;无需 sudo;只读列出当前用户启动项
ls -la ~/Library/LaunchAgents | grep -i sing
# macOS;需要 sudo;只读列出系统启动项
ls -la /Library/LaunchAgents /Library/LaunchDaemons | grep -i sing只有在已经确认 plist 路径和服务域之后,才考虑卸载。若 plist 配置了 KeepAlive,仅执行 launchctl stop 可能导致它再次启动;处理“停止后马上又占端口”的情况,应先使用与实际域匹配的 bootout,再检查 lsof。不要照抄不存在的 label;服务 label 必须从实际 plist 或 launchctl list 输出中确认。
# system 域示例;需要 sudo;把 <LABEL> 换成已核实的服务 label
sudo launchctl bootout system/<LABEL>
# 当前用户 GUI 域示例;无需 sudo;把 <UID> 和 <LABEL> 换成实际值
launchctl bootout gui/$(id -u)/<LABEL>如果使用 SFM,先完成应用内停止,再检查是否还有 CLI 实例或独立 LaunchDaemon;不要因为客户端使用 NetworkExtension,就假设一定存在一个可直接 kill 的 root sing-box 进程。
改端口后,必须同步检查这几个引用
如果原端口确实被其他服务占用,修改配置中的 listen_port 或 external_controller 后,要同步更新使用方。sing-box 监听字段文档列出了 listen、listen_port 和 reuse_addr 等字段,Clash API 则单独使用 external_controller,两者不要混成一个端口。
- mixed、SOCKS 或 HTTP 入站:检查对应
inbounds[].listen和inbounds[].listen_port,并同步系统代理、浏览器代理或其他本机客户端中的地址端口。 - Clash API:检查
experimental.clash_api.external_controller,同步更新 Yacd、MetaCubeXD 等使用该 API 地址的管理界面;不要把服务端面板当作 API 本身。 - TUN:不要为了 TUN 入站凭空修改一个不存在的
listen_port;按日志和实际配置检查。 - 验证配置:在启动前运行配置检查命令,输出应关注是否还有 JSON、字段或启动错误。
# macOS;无需 sudo;检查配置语法和字段
sing-box check -c /path/to/config.json提交日志到论坛或 issue 前,删除订阅链接、服务器域名或 IP、UUID、密码、Bearer secret 以及可能暴露个人网络环境的字段。只保留系统版本、sing-box 版本、错误行和已脱敏的监听字段,避免为了排障泄露凭据。
常见问题
为什么等了 60 秒仍然提示 address already in use?
先重新运行带 sudo 的 TCP lsof,排除有进程在自动重启;再检查是否是另一个地址族或另一个入站占用了同一端口。如果存在 LaunchDaemon 或 LaunchAgent,KeepAlive 可能让服务重新拉起,需根据实际 label 和域检查并在必要时用 bootout 卸载。若端口没有监听者,换一个临时端口验证配置,能帮助区分端口状态问题和配置本身的问题。
root 启动后改普通用户,为什么更容易遇到这个错误?
公开 issue #4650 报告了这种现象:在 macOS 27.0.1、sing-box 1.14.3 的复现中,root 实例接受连接并退出后,普通用户立即绑定相同端口会失败约 30 秒,连接仍保持时报告最长约 60 秒。这里应按“公开案例中的可复现现象”理解,不要把它扩展成所有版本和所有配置都必然发生。处理上优先统一运行用户,或等待并换端口验证。
SFM 和命令行 sing-box 可以同时运行吗?
可以同时存在,但如果两者配置使用同一个监听地址和端口,就会冲突。SFM 优先在应用内停止服务并查看 Settings → View Service Log;命令行实例优先在前台终端按 Control-C,然后用 lsof 确认监听已经消失。两者都停止后再启动其中一个,不要直接批量结束所有网络进程。
reuse_addr 能不能直接解决 root 切换后的端口占用?
不能保证。官方 Listen Fields 文档显示该字段自 sing-box 1.12.0 起提供,但公开 issue #4650 讨论的是 macOS Darwin 下不同 UID 的重绑定现象,不能从文档或 issue 推出它一定解决跨用户场景。当前版本和入站类型支持时可以作为低风险测试项;无效时按等待、统一用户或更换端口处理。
tun 入站会不会占用 mixed 入站的 listen_port?
不能仅因为启用了 TUN 就这样判断。TUN 入站与 TCP/UDP 监听入站的字段和工作方式不同,排查应以实际错误日志中的地址、协议和端口为准。若日志指向 mixed、SOCKS、HTTP 或 Clash API,就检查对应的监听字段;若日志没有端口,继续看服务日志中的第一条失败原因。