エラーの内容
ファイルアップロードのエンドポイントが500エラーを返しています。/var/log/nginx/error.logを開くと、次のようなログが見つかるはずです:
open() "/var/lib/nginx/tmp/client_body/0000000001" failed (13: Permission denied) while reading client request body, client: 203.0.113.45, server: example.com, request: "POST /upload HTTP/1.1"
不思議なことに、小さいファイルのアップロードは問題なく動作します。ファイルサイズが大きくなった場合——通常は数キロバイトを超えたとき——だけ500エラーが発生します。これが手がかりです。
なぜこのエラーが起きるのか
Nginxは大きなPOSTボディをアプリに直接ストリームしません。まず一時ファイルとしてclient_body_temp_path(デフォルト:/var/lib/nginx/tmp/client_body/)にバッファリングしてから、上流に渡します。ワーカープロセスがそのディレクトリの所有者でなければ、書き込みが失敗してpermission deniedエラーになります。
原因はほぼ必ず、以下の4つのいずれかです:
- パッケージのソースを変更した場合——例えば、ディストリビューションのNginxリポジトリから公式Nginxリポジトリに移行したとき。デフォルトの実行ユーザーが
www-dataからnginx(またはその逆)に変わったのに、一時ディレクトリの所有権が追従しなかった /var/lib/nginx/ディレクトリが誤った所有者でバックアップから再作成または復元されたnginx.confのuserディレクティブを手動で変更したが、ディレクトリの所有権を更新しなかった- サーバー移行時にtmpディレクトリが初期化されなかった
ステップ1 — 根本原因を確認する
まず、Nginxのワーカーが実際にどのユーザーで動いているか確認します:
ps aux | grep nginx | grep worker
出力は次のどちらかになります:
www-data 1234 0.0 0.1 nginx: worker process
# または
nginx 1234 0.0 0.1 nginx: worker process
次に、一時ディレクトリの所有者を確認します:
ls -la /var/lib/nginx/tmp/
この2つが一致していなければ、それが原因です。例えば:
drwx------ 2 nginx nginx 40 Apr 21 02:14 client_body
# ワーカーがwww-dataで動いている場合、これが不一致
ステップ2 — パーミッションを修正する
所有権をワーカーの実行ユーザーに合わせて変更します。Ubuntu/Debianでは通常www-data、CentOS/RHELでは通常nginxです:
# Ubuntu/Debian
chown -R www-data:www-data /var/lib/nginx/
# CentOS/RHEL
chown -R nginx:nginx /var/lib/nginx/
tmpサブディレクトリ自体が存在しない場合は、先に作成してください:
mkdir -p /var/lib/nginx/tmp/client_body
chown -R www-data:www-data /var/lib/nginx/tmp/
chmod 700 /var/lib/nginx/tmp/client_body
ステップ3 — nginx.confのuserディレクティブを確認する
設定ファイルが間違ったユーザーを指したままでは、パーミッションを修正しても効果が持続しません:
grep -E '^user' /etc/nginx/nginx.conf
この行は実際の環境と一致している必要があります。Ubuntuでuser nginx;と書かれているのにNginxがwww-dataでインストールされている場合は修正してください:
# /etc/nginx/nginx.conf
user www-data; # 実際のシステムユーザーと一致させること
ついでに、一時パスを明示的に設定しておくことをお勧めします——設定の意図が明確になり、後のデバッグがしやすくなります:
http {
client_body_temp_path /var/lib/nginx/tmp/client_body 1 2;
client_max_body_size 100m;
# ... 残りの設定
}
ステップ4 — Nginxをリロードする
# まず設定をテスト
nginx -t
# クリーンリロード — ダウンタイムなし
systemctl reload nginx
ステップ5 — 修正を確認する
ターミナルでエラーログを監視し続けます:
tail -f /var/log/nginx/error.log
別のターミナルで、実際のサイズのファイルをアップロードしてみます:
# 50MBのテストファイルを生成してアップロード
dd if=/dev/urandom of=/tmp/testfile bs=1M count=50
curl -X POST https://example.com/upload \
-F "file=@/tmp/testfile" \
-w "\nHTTP Status: %{http_code}\n"
200レスポンス(またはアプリが成功時に返すステータスコード)が返り、エラーログに何も出なければ完了です。
別の修正方法 — 一時パスをリダイレクトする
所有権の変更が難しい場合、コンテナ環境などでよくあることですが、書き込み可能な場所をNginxに指定してください:
http {
client_body_temp_path /tmp/nginx_client_body 1 2;
# ...
}
その後、ディレクトリを設定します:
mkdir -p /tmp/nginx_client_body
chown www-data:www-data /tmp/nginx_client_body
nginx -t && systemctl reload nginx
注意点:/tmpはリブート時に消去されます。本番環境では、再起動後も残るパスを選んでください。
補足Tips
Nginxパッケージのアップグレード後
ディストリビューションのリポジトリと公式Nginxリポジトリを切り替えると、デフォルトの実行ユーザーがひっそりと変わります。Nginxのアップグレード後は、これを習慣にしてください:
NGINX_USER=$(ps aux | grep 'nginx: worker' | grep -v grep | awk '{print $1}' | head -1)
echo "Nginx worker user: $NGINX_USER"
chown -R $NGINX_USER:$NGINX_USER /var/lib/nginx/
Dockerコンテナの場合
カスタムDockerイメージでは/var/lib/nginx/tmp/の作成が省略されることがよくあります。Dockerfileにこの2行を追加すれば、問題を未然に防げます:
RUN mkdir -p /var/lib/nginx/tmp/client_body \
&& chown -R nginx:nginx /var/lib/nginx/
chmodの値がわからない場合
ToolCraftのUnix Permissions Calculatorを使うと、8進数の値を視覚的に確認できます——700を入力すると、オーナー・グループ・その他に何が許可されているか一目でわかります。ブラウザ上で完結します。
Ansible/Terraformで固める
サーバーを自動プロビジョニングしている場合、所有権の修正をべき等に実行するようにしておくと、デプロイのたびに確実に適用されます:
# Ansibleタスク
- name: Ensure nginx tmp directory has correct ownership
file:
path: /var/lib/nginx/tmp/client_body
state: directory
owner: "{{ nginx_user }}"
group: "{{ nginx_user }}"
mode: '0700'
recurse: yes

