Go一启动就报too many open files,说明进程未进入main(),内核fd软限制已低于8192;需通过/proc/<pid>/limits查真实Soft limit值,而非ulimit -n,并在main()开头用unix.Getrlimit校验。

Go一启动就报too many open files,说明进程连main()都没进,内核软限制(Soft limit)已低于8192——这不是代码问题,是系统参数根本没对齐。
查清 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) } }
Linux 系统级 ulimit 必须在 Go 启动前固定
单纯ulimit -n 65535只对当前 shell 有效;写进/etc/security/limits.conf后必须重新登录才生效;而 supervisor、systemd、docker 这些 init 管理器,往往有自己的默认限制,且不自动继承宿主机配置。
-
supervisor:必须在
[program:xxx]段显式加minfds=65535,否则默认是 1024 -
systemd:在 service 文件中加
LimitNOFILE=65535,并确认DefaultLimitNOFILE没被覆盖 -
Docker:用
--ulimit nofile=65536:65536,但若 entrypoint 是 shell 脚本,得改用exec ./app直接替换 PID 1 -
K8s:
securityContext.ulimits必须作用于主容器的 PID 1,initContainer 改的只是自己进程的/proc,不影响主进程
http.Server 默认配置是启动即崩的高危点
没设超时的http.Server在启动 accept 阶段就会卡住 fd:一个恶意空连接、一个慢客户端握手未完成,就能让 listener 卡在accept系统调用里,fd 持续占用不释放,直到超时(默认 3 分钟)。这比 handler 里漏关更早触发too many open files。
-
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 会越积越多
真正难定位的是那些“持有 fd 却迟迟不释放”的 goroutine——比如阻塞在syscall.Read或卡在os.File.Close的协程,它们不会出现在/proc/pid/limits里,但会让lsof -p <pid>持续上涨。这时候得靠runtime.SetBlockProfileRate(1) + /debug/pprof/goroutine?debug=2去抓现场。

















