15.

Laravel / PHP 画像アップロード時の 500 Internal Server Error 原因と修正チェックリスト

編集
この記事の要点
  • 第一に Laravel ログを見る: tail -f storage/logs/laravel.log。500 は必ず原因がスタックトレースに出る
  • サイズ上限: php.ini の upload_max_filesize / post_max_size / memory_limit、Nginx の client_max_body_size
  • storage 権限: chmod -R 775 storage bootstrap/cachechown -R www-data:www-data
  • storage:link 忘れ: php artisan storage:link。public/storage シンボリックリンクが無いと参照できない
  • 画像ライブラリ未導入: Intervention Image / GD / Imagick が無いと resize で 500(apt install php-gd

まず最初に: 真のエラーをログで確認

500 Internal Server Error はブラウザ側には詳細が出ません。必ずサーバ側ログを見ます:

# Laravel 本体のログ
tail -f /var/www/myapp/storage/logs/laravel.log

# PHP-FPM の致命的エラー
sudo tail -f /var/log/php8.2-fpm.log

# Nginx エラーログ(リクエスト弾かれるとここ)
sudo tail -f /var/log/nginx/error.log

# Apache の場合
sudo tail -f /var/log/apache2/error.log

# システム全体
sudo journalctl -xe -u nginx -u php8.2-fpm

laravel.log に何も出ていない場合はLaravel に到達する前で死んでいる可能性が高く、PHP-FPM / Nginx 側のエラーを見ます。

原因 1: アップロードサイズ上限

最も多いのがファイルサイズ上限超過。複数の場所で制限がかかっています:

場所設定既定推奨
php.iniupload_max_filesize2M20M〜
php.inipost_max_size8Mupload_max_filesize 以上
php.inimemory_limit128M256M〜
php.inimax_execution_time30120
Nginxclient_max_body_size1M20M〜
ApacheLimitRequestBody無制限必要に応じ
# 現在値を確認
php -i | grep -E "upload_max_filesize|post_max_size|memory_limit"

# または PHP コードで

php.ini の修正

; /etc/php/8.2/fpm/php.ini

upload_max_filesize = 20M
post_max_size = 25M
memory_limit = 256M
max_execution_time = 120
max_input_time = 120
file_uploads = On
# PHP-FPM 再起動
sudo systemctl restart php8.2-fpm

Nginx の修正

# /etc/nginx/sites-available/myapp

server {
    listen 80;
    server_name example.com;
    client_max_body_size 25M;   # ★ 追加

    location / {
        ...
    }
}
sudo nginx -t
sudo systemctl reload nginx

原因 2: storage / public のパーミッション

Laravel は storage/ 配下にファイルを書き込みます。Web サーバユーザー(www-data / nginx)に書き込み権限が必要:

cd /var/www/myapp

# 所有者を Web サーバユーザーに
sudo chown -R www-data:www-data storage bootstrap/cache

# 書き込み権限
sudo chmod -R 775 storage bootstrap/cache

# シンボリックリンク作成
php artisan storage:link
# → public/storage → ../storage/app/public が作られる

# 確認
ls -l public/storage
# public/storage -> /var/www/myapp/storage/app/public

権限不足の場合 laravel.log に次のように出ます:

[2026-05-17 10:23:14] production.ERROR: file_put_contents(/var/www/myapp/storage/app/public/uploads/xxx.jpg): Failed to open stream: Permission denied

原因 3: storage:link 忘れ

ファイルは保存できているのにURL で開けない場合、storage:link 未実行が原因:

php artisan storage:link

# 既に存在エラーが出る場合
ls -l public/storage
# 壊れたリンクなら一度削除
rm public/storage
php artisan storage:link

原因 4: GD / Imagick 未インストール

Intervention Image などでresize / crop / 変換を行うと、GD か Imagick が必要です:

# 確認
php -m | grep -E "gd|imagick"

# Ubuntu/Debian
sudo apt install php8.2-gd php8.2-imagick

# RHEL/CentOS
sudo dnf install php-gd php-imagick

# 再起動
sudo systemctl restart php8.2-fpm

# 確認
php -r "var_dump(extension_loaded('gd'), extension_loaded('imagick'));"

未インストールだとログに:

Intervention\Image\Exception\NotSupportedException: Encoder format (jpeg) is not supported.
# または
Class "Imagick" not found

原因 5: validation で 422 ではなく 500 になっている

本来バリデーション失敗は 422 だが、コードの作りで 500 になっていることも:

file('image')->getClientOriginalExtension();
    // → Call to a member function getClientOriginalExtension() on null
}

// ✅ OK: 必ず validate してから
public function store(Request $request)
{
    $validated = $request->validate([
        'image' => 'required|image|max:5120',  // 5MB
    ]);

    $path = $request->file('image')->store('uploads', 'public');
    return response()->json(['path' => $path]);
}

原因 6: SELinux(CentOS / RHEL)

# storage/ への書き込みが SELinux にブロックされる
sudo setsebool -P httpd_unified 1
sudo chcon -R -t httpd_sys_rw_content_t /var/www/myapp/storage
sudo chcon -R -t httpd_sys_rw_content_t /var/www/myapp/bootstrap/cache

# 一時的に確認
sudo setenforce 0   # Permissive
# ↑ で直るなら SELinux 起因

切り分けチェックリスト

  1. storage/logs/laravel.log 確認
  2. PHP-FPM / Nginx エラーログ確認
  3. ファイルサイズが上限内か(5MB なら 5242880 byte)
  4. php -i | grep upload
  5. ls -l storage/ public/storage で権限と symlink
  6. php -m | grep gd
  7. php artisan storage:link 実行
  8. SELinux / AppArmor 確認
  9. ディスク容量 df -h
  10. inode 残量 df -i

典型的な正しい実装

validate([
            'image' => 'required|image|mimes:jpeg,png,jpg,webp|max:5120',
        ]);

        $file = $request->file('image');
        $filename = Str::uuid() . '.' . $file->getClientOriginalExtension();

        // リサイズして保存
        $img = Image::make($file)->resize(1200, null, function ($c) {
            $c->aspectRatio();
            $c->upsize();
        });

        Storage::disk('public')->put("uploads/{$filename}", (string) $img->encode());

        return response()->json([
            'url' => Storage::url("uploads/{$filename}"),
        ]);
    }
}

FAQ

Q: laravel.log に何も出ない
A: ① 権限不足で laravel.log 自体が書けない、② エラーが Laravel に到達する前(Nginx で弾かれ)、③ APP_DEBUG=false + コード内 catch で握り潰し。

Q: ローカルでは動くが本番で 500
A: 本番の php.ini / Nginx 設定値、GD/Imagick 有無、storage 権限を本番側で再確認。php artisan config:clear も忘れずに。

Q: 大きい画像で OOM Killer に殺される
A: memory_limit を 512M に。または resize 前に画像サイズチェック・サーバ側で imagick の policy.xml を緩める。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. SQLSTATE[HY000] [1045] Access denied for user 'homestead'@'localhost'
  2. Add [~] to fillable property to allow mass assignment on [App\~].
  3. PHP Parse error: syntax error, unexpected 'class' (T_CLASS), expecting identifier (T_STRING) or variable (T_VARIABLE) or '{' or '$' in ~
  4. Changing columns for table "~" requires Doctrine DBAL; install "doctrine/dbal"
  5. MethodNotAllowedHttpException No message
  6. Class 'Doctrine\DBAL\Driver\PDOMySql\Driver' not found
  7. production.ERROR: No application encryption key has been specified.
  8. Dotenv values containing spaces must be surrounded by quotes.
  9. Laravel \ Socialite \ Two \ InvalidStateException
  10. The page has expired due to inactivity. Please refresh and try again.
  11. Failed to clone https://github.com/symfony/thanks.git via https, ssh protocol
  12. Illegal offset type
  13. Cannot access protected property Illuminate\Http\Request::$...
  14. Emitted value instead of an instance of Error
  15. 画像保存時にInternal Server Error
  16. Failed to authenticate on SMTP server with username ...
  17. PostTooLargeException
  18. Database hosts array is empty.
  19. Invalid request (Unsupported SSL request)
  20. does not comply with psr-4 autoloading standard. Skipping.
  21. MySQLのSTR_TO_DATE関数を使用するとnullが返却される問題