Go二进制一执行就崩在accept4: too many open files,说明进程启动瞬间因fd软限过低导致runtime初始化失败,未进入main();需通过/proc/<pid>/limits查真实Soft limit,而非ulimit -n,并在main开头用unix.Getrlimit校验是否≥8192。

Go二进制一执行就崩在 accept4: too many open files,说明它根本没跑进 main() —— 是系统 fd 软限太低,runtime 初始化阶段直接失败。这不是代码漏关、也不是超时没设,而是进程启动瞬间就被卡死。
查清 Go 进程真实的 fd 限制值
别信 ulimit -n 输出,它只反映当前 shell 的软限。Go runtime 启动时读的是 /proc/self/limits,且只读一次。容器里尤其容易错判:你看到 docker run --ulimit,但若 entrypoint 是 sh -c "exec ./app",shell 会重置限制,Go 实际拿到的还是默认 1024。
- 进容器后先找 PID:
pidof yourapp或ps aux | grep yourapp - 看真实生效值:
cat /proc/<pid>/limits | grep "Max open files",重点看第二列(Soft limit) - 在
main()开头加校验(需// +build !windows):var rlim unix.Rlimit if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlim); err == nil { if rlim.Cur < 8192 { log.Fatal("fd soft limit too low:", rlim.Cur) } }
systemd、supervisor、Docker、K8s 各自的 ulimit 生效点
这些 init 管理器不自动继承宿主机 /etc/security/limits.conf,必须显式配置,且要确保作用于 PID 1(即你的 Go 二进制本身)。
-
systemd:在 service 文件中加
LimitNOFILE=65535;确认没被DefaultLimitNOFILE覆盖 -
supervisor:必须在
[program:xxx]段显式加minfds=65535,否则默认是 1024 -
Docker:用
--ulimit nofile=65535:65535,但 entrypoint 若为 shell,会重置——改用exec ./app直接替换 PID 1 -
K8s:
securityContext.ulimits必须作用于主容器(不是 initContainer),且 initContainer 改的只是自己进程的/proc,不影响主进程
http.Server 默认配置是启动即崩的高危点
没设超时的 http.Server 在启动 accept 阶段就会卡住 fd:一个恶意空连接、一个慢客户端握手未完成,就能让 listener 卡在 accept 系统调用里,fd 持续占用不释放,直到超时(默认 3 分钟)。
-
ReadTimeout和WriteTimeout必须显式设(如30 * time.Second),否则单请求可无限期占着 fd -
IdleTimeout设为60–120 * time.Second,比反向代理(如 Nginx)的keepalive_timeout小 10 秒,避免半开连接堆积 - Go 1.19+ 可用
MaxConns硬限总连接数,但它统计的是所有连接(含空闲),适合突发压制,不能替代超时
os.Open / net.Listen 后没 Close 是最常见启动失败原因
不是“并发高才爆”,而是启动阶段反复 os.Open 配置文件、os.CreateTemp 日志目录、net.Listen 多端口监听,只要漏一个 Close(),fd 就永久泄漏。GC 完全不回收 fd,defer 也救不了被 panic 或 os.Exit 跳过的场景。
- 所有
os.Open*调用后必须紧跟defer f.Close(),哪怕只读一次;循环里别用os.Open → io.ReadAll,改用os.ReadFile(内部自动开闭) -
net.Listen是资源型对象,热更新或测试代码里反复Listen却忘了l.Close(),fd 会越积越多;正确做法是全局复用一个 listener - 用
lsof -p <pid>快速筛泄漏源:lsof -p <pid> | grep -E '\.(json|yaml|log|tmp)$'找文件类 fd;lsof -p <pid> | grep 'TCP\|UDP'看连接类 fd
真正难定位的是“哪些 goroutine 卡着 fd 不放”:当 lsof -p <pid> | wc -l 持续上涨,但 unix.Getrlimit 显示软限充足,说明泄漏发生在运行时——比如阻塞在 os.File.Close 或 syscall.Read 的 goroutine。这时候 defer f.Close() 写了也没用,因为调用根本没执行到那一步。


















