要让 error_log 捕获“too many open files”错误,必须协同配置 php 日志路径、ulimit 进程限制(/etc/security/limits.conf 或 systemd limitnofile)及系统级 fs.file-max,否则 fd 耗尽时日志无法写入。

要让 error_log 真正捕获到 “too many open files” 错误,不能只靠写日志,必须先确保 PHP 进程本身有足够文件描述符可用;否则错误还没来得及记录,进程就因无法打开日志文件而静默失败。核心是:日志配置和内核限制必须协同生效。
让 error_log 能真正写入的关键配置
PHP 的 error_log 是个“依赖型”功能——它需要一个可写的日志文件路径,而该路径的打开、追加操作本身就要占用一个文件描述符(FD)。如果进程 FD 已耗尽,连日志都打不开,自然无法记录错误。
- 确认日志目录可写且不跨挂载点(避免 NFS 或只读文件系统导致 write 失败)
- 在
php.ini中明确指定绝对路径:error_log = /var/log/php/error.log,不要用相对路径或syslog - 提前创建日志文件并赋权:
sudo mkdir -p /var/log/php && sudo touch /var/log/php/error.log && sudo chown www-data:www-data /var/log/php/error.log - 启用
log_errors = On,并设error_reporting = E_ALL(开发环境)或E_ALL & ~E_NOTICE & ~E_WARNING(生产环境)
为什么单配 error_log 不够?必须联动 ulimit 和 file-max
“Too many open files” 是内核返回的 EMFILE 错误,发生在 PHP 调用 open()、socket()、fopen() 等系统调用时。此时 PHP 只能抛出警告或致命错误,能否记录下来,取决于当时是否还有 FD 剩余用于写日志。
- 若 PHP-FPM 子进程的
nofile限制仍是默认 1024,而每个请求打开 5 个文件(日志、缓存、DB 连接、临时文件、curl socket),那么不到 200 并发就可能耗尽 FD - 即使你配了
error_log,当第 1024 个 FD 被占满后,PHP 尝试写日志会再次触发EMFILE,形成死循环或直接崩溃 - 所以必须先提升进程级限制:
www-data soft nofile 65535和www-data hard nofile 65535写入/etc/security/limits.conf,并确保 PHP-FPM 是以该用户身份启动(非 root)
systemd 服务需单独声明 LimitNOFILE(常被忽略)
如果你用 systemd 管理 PHP-FPM(如 php7.4-fpm.service),/etc/security/limits.conf 对它完全无效。systemd 有自己的资源控制机制。
- 编辑服务文件:
sudo systemctl edit php7.4-fpm(推荐方式,避免修改原始文件) - 添加以下内容:
[Service] LimitNOFILE=65535
- 重载并重启:
sudo systemctl daemon-reload && sudo systemctl restart php7.4-fpm - 验证是否生效:
cat /proc/$(pgrep php-fpm)/limits | grep "Max open files",输出应显示65535
配合系统级 file-max 防止全局耗尽
单个进程开再多 FD,也受限于整个系统的 fs.file-max。尤其在多服务共存环境(PHP-FPM + MySQL + Nginx),总 FD 消耗很容易突破默认值(如 80 万)。
- 查看当前值:
cat /proc/sys/fs/file-max - 临时提升:
sudo sysctl -w fs.file-max=2097152 - 永久生效:向
/etc/sysctl.conf追加fs.file-max = 2097152,再执行sudo sysctl -p - 注意:此值不宜设得过高(如超 400 万),否则可能引发内存压力或 inode 表膨胀,建议按实际并发 × 10~15 倍估算










