高トラフィックサーバーの「Too many open files」(EMFILE)エラーを修正する

intermediate🌐 Networking2026-04-20| Linux (Ubuntu 20.04/22.04、CentOS 7/8、Debian)、nginx、Node.js、Apache、高接続数のTCPサーバー全般

Error Message

Error: accept(2) failed: Too many open files (EMFILE)
#linux#ネットワーク#ulimit#ソケット#サーバー

エラーの内容

Error: accept(2) failed: Too many open files (EMFILE)

サーバーがファイルディスクリプタの上限に達し、新しい接続を受け付けられなくなっています。Linuxでは、開いているソケットはすべてFDとしてカウントされます — TCPコネクション、UDPソケット、Unixソケット、通常のファイルはすべて同じプールから消費されます。このプールが枯渇すると、accept(2)EMFILEを返し、クライアントへの接続が拒否されます。

主に高負荷時に発生します:ストレステスト、急激なトラフィックの急増、または何時間もかけて徐々に積み重なったコネクションリークなどが原因です。

根本原因

Linuxはオープンファイルディスクリプタに対して2つの制限を課しています:

  • プロセスごとの制限ulimit -n)— 単一プロセスが保持できるFDの数。CentOS 7および古いディストリビューションではデフォルト1024、Ubuntu 22.04以降および新しいRHELシステムでは65536。
  • システム全体の制限/proc/sys/fs/file-max)— ホスト上のすべてのプロセスを合わせた合計FD数。

ほとんどの場合、プロセスごとの制限が原因です。600の同時接続を処理しているnginxワーカーは、数秒で1024 FDを超えてしまいます — クライアントへの接続が拒否されるまで気づかないことがほとんどです。

まず診断する

何かを変更する前に、影響を受けているプロセスが実行時に実際に認識している制限を確認しましょう:

# PIDを確認
pgrep nginx  # または node、apache2 など

# 現在のFD制限を確認
cat /proc/<PID>/limits | grep 'open files'

# 現在使用中のFD数を確認
ls /proc/<PID>/fd | wc -l

# システム全体の現在の使用量と上限
cat /proc/sys/fs/file-nr
# 出力: [使用中FD数] [0] [最大FD数]

現在の使用量が/proc/<PID>/limitsに表示されている制限に近い場合、それが問題です。コネクションリークも確認しましょう:

# プロセスのソケットを状態別にカウント
ss -s

# 特定ポートのすべての接続を確認
ss -tnp | grep ':8080'

# TIME_WAITはどれだけ積み重なっている?
ss -tan | grep TIME-WAIT | wc -l

TIME_WAITCLOSE_WAITの接続が数千件あれば、予想以上に長くFDが保持されている理由がわかります。まずリークを修正してください — 制限を引き上げても問題を先送りするだけです。

修正1: プロセスごとのulimitを引き上げる(即時対応)

稼働中のシステムへの即時対応として、現在のシェルに制限を設定してサービスを再起動します:

ulimit -n 65536

これは現在のシェルセッションにのみ適用されます。再起動後も設定を維持するには、/etc/security/limits.confを編集します:

# /etc/security/limits.conf
*    soft    nofile    65536
*    hard    nofile    65536

# または特定ユーザーを対象にする(例:nginxの場合はwww-data)
www-data    soft    nofile    65536
www-data    hard    nofile    65536

設定を有効にするには、一度ログアウトして再ログイン(またはサービスを再起動)してください。設定が反映されているか確認します:

su - www-data -s /bin/bash -c 'ulimit -n'

修正2: systemdユニットファイルに制限を設定する

最近のLinuxサーバーの多くでは、limits.confは実際には効果がありません。systemdサービスはPAMを完全にバイパスすることが多く、そのファイルのユーザーごとの設定がサービスプロセスに適用されないためです。ユニットファイルに直接制限を設定しましょう:

# オーバーライドファイルを作成
systemctl edit nginx

# エディタに以下を追加:
[Service]
LimitNOFILE=65536

または手動でファイルを配置します:

# /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=65536

その後、リロードして再起動します:

systemctl daemon-reload
systemctl restart nginx

# 新しい制限が有効になっているか確認
cat /proc/$(pgrep nginx | head -1)/limits | grep 'open files'

修正3: システム全体の制限を引き上げる

多数のサービスを実行している場合、またはプロセスごとの制限はすでに高いのにエラーが発生し続ける場合は、システム全体の上限がボトルネックになっているかもしれません:

# 現在のシステム全体の最大値を確認
cat /proc/sys/fs/file-max

# 一時的に引き上げる
sysctl -w fs.file-max=2097152

# 永続的に設定する
echo 'fs.file-max = 2097152' >> /etc/sysctl.conf
sysctl -p

修正4: nginx固有の設定

nginxには、ワーカープロセスのOS制限を上書きする独自のworker_rlimit_nofileディレクティブがあります:

# /etc/nginx/nginx.conf
worker_processes auto;
worker_rlimit_nofile 65536;

events {
    worker_connections 4096;  # ワーカープロセスごとの接続数
    use epoll;
    multi_accept on;
}

計算式:worker_connections × worker_processes × 2 ≤ worker_rlimit_nofile。各接続は最低2つのFDを使用します — クライアントソケット用に1つ、アップストリームまたは配信するファイル用に1つ。

修正5: Node.jsサーバー

Node.jsは起動したプロセスからFD制限を継承します — Node固有の設定はありません。2,000の同時WebSocket接続を処理するサーバーは、DBコネクション、ログファイルハンドルなどを除いても、それらのソケットだけで最低2,000 FDが必要です。nodeを起動するシェルまたはsystemdユニットに正しい制限が設定されていることを確認してください。起動時にログに記録して確認することもできます:

const { execSync } = require('child_process');

// 起動時にFD制限をログに記録
try {
  const limit = execSync('ulimit -n').toString().trim();
  console.log(`FD limit: ${limit}`);
} catch (e) {
  console.error('Could not read FD limit');
}

PM2を使用している場合、アプリの設定だけでは不十分です — PM2のsystemdサービスにもLimitNOFILEを設定してください:

# ecosystem.config.js
module.exports = {
  apps: [{
    name: 'myapp',
    script: 'server.js',
    node_args: '--max-old-space-size=2048',
    env: {
      NODE_ENV: 'production'
    }
  }]
};

# /etc/systemd/system/pm2-root.service.d/override.conf に以下を追加:
# [Service]
# LimitNOFILE=65536

修正の確認

再起動後、制限が実際に有効になっているか確認してください — 思い込みは禁物です:

# 新しい制限が有効になっているか確認
cat /proc/$(pgrep -f 'nginx: master' | head -1)/limits | grep 'open files'

# 負荷をかけながらFD使用量をリアルタイムで監視
watch -n1 'ls /proc/$(pgrep nginx | head -1)/fd | wc -l'

# 簡単な負荷テストを実行してEMFILEが発生しないことを確認
# (インストール: sudo apt install apache2-utils)
ab -n 10000 -c 500 http://localhost/

# エラーログを確認 — クリーンになっているはず
tail -f /var/log/nginx/error.log

予防策

再発を防ぐために役立った対策をいくつか紹介します:

  • limits.confだけでなく、systemdユニットにも制限を設定する。 systemd管理のシステムでは、ユニットファイルが優先されます。常にLimitNOFILEをそこに設定してください。
  • FD使用量を監視する。 いずれかのプロセスがFD制限の80%を超えたらアラートを出します。Prometheusのnode_exporternode_filefd_allocatednode_filefd_maximumを公開しています — (node_filefd_allocated / node_filefd_maximum) > 0.8のようなシンプルなアラートルールで、問題が起きる前に早期警告を得られます。
  • マルチワーカーサーバーでSO_REUSEPORTを有効にすることで、acceptの負荷をワーカー間で分散し、1つのワーカーのFD予算が枯渇するリスクを減らせます。
  • 大量の残留コネクションがFDを消費している場合はTIME_WAITのリサイクルを調整する:

/etc/sysctl.conf

net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 15

  
  - **アプリのコネクション処理を見直す。** HTTPクライアント、DBコネクションプール、ファイルハンドルは明示的にクローズする必要があります。コネクションリークは、制限を十分に高く設定していてもFDを枯渇させます — 何もクリーンアップしなければ65536 FDもあっという間になくなります。

どのIPレンジがサーバーに大量のリクエストを送っているか調べる際は、[ToolCraftのサブネット計算ツール](https://toolcraft.app/en/tools/developer/ip-subnet-calculator)がCIDRレンジの素早い算出に便利です — ブラウザ上で完結し、データのアップロードは一切不要です。

Related Error Notes