You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel 9部署Amazon Linux EC2时文件上传报403 Forbidden错误排查求助

Laravel 9 EC2部署后文件上传返回HTTP 403错误排查指南

已知情况

  • 本地环境文件上传功能正常,部署到Amazon Linux EC2服务器后,上传图片操作返回HTTP 403错误,无异常抛出(已开启.env的debug模式)
  • 仅上传图片时触发错误,其他控制器功能运行正常

疑似问题

403错误在到达ContactController的update方法前触发(已在方法第一行添加dd()但未执行),推测问题出在Apache/服务器配置层面。

已执行排查步骤

  • 确认S3存储桶策略正常(本地同控制器可正常操作S3)
  • 验证服务器根目录权限无误
  • 确认UpdateContactRequest请求类的authorize()方法返回true
  • 查看错误日志未发现相关记录
  • 确认php.ini配置:file_uploads=On、upload_max_filesize=4M、max_file_uploads=20

相关代码与配置

控制器代码

ContactController的update方法

//https://myapp.com/contacts/1
//App\Http\Controllers\ContactController
public function update(UpdateContactRequest $request, Contact $contact)
{
    dd($request); //未执行,403在到达此处前触发

    $contact->update($request->all());

    $this->uploadAvatar($request, $contact);

    Flash::success('Contact updated successfully.');

    return redirect(route('dealers.contacts.index', $contact->dealer->id));
}

uploadAvatar函数

private function uploadAvatar(Request $request, Contact $contact)
{
    if ($request->hasFile('avatar')) {
        try {
            $contact
                ->addMediaFromRequest('avatar')
                ->sanitizingFileName(function ($fileName) {
                    return strtolower(str_replace(['#', '/', '\\', ' '], '-', $fileName));
            })
            ->toMediaCollection('avatars');
        } catch (\Spatie\MediaLibrary\MediaCollections\Exceptions\FileUnacceptableForCollection $e) {
            Flash::error($e->getMessage());
        }
    }
}

路由定义

Route::get('/', function () {
    return view('welcome');
});

Route::get('/test', App\Http\Controllers\TestController::class);
Route::get('/embed-iframe/{uuid}', [App\Http\Controllers\EmbedController::class, 'iframe']);
Route::get('/embed-js/{uuid}', [App\Http\Controllers\EmbedController::class, 'js']);
Auth::routes();

Route::middleware('admin')->group(function () {
    Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
    Route::resource('dealers', App\Http\Controllers\DealerController::class);
    Route::post('refreshDealerCRMData', [App\Http\Controllers\DealerController::class, 'refreshCRMData']);
    Route::post('loadCRMView', [App\Http\Controllers\DealerController::class, 'loadCRMView']);
    Route::resource('cms', App\Http\Controllers\CmsController::class);
    Route::resource('crms', App\Http\Controllers\CrmController::class);
    Route::resource('leads', App\Http\Controllers\LeadController::class);
    Route::resource('contacts', App\Http\Controllers\ContactController::class);
    Route::resource('attachment-categories', App\Http\Controllers\AttachmentCategoryController::class);
    Route::resource('CRMAttachments', App\Http\Controllers\CRMAttachmentController::class);
    Route::resource('dealers.leads', App\Http\Controllers\DealerLeadController::class)->scoped([
        'dealers' => 'dealer.id',
        'leads' => 'lead.id',
    ]);
    Route::resource('dealers.contacts', App\Http\Controllers\DealerContactController::class)->scoped([
        'dealers' => 'dealer.id',
        'contacts' => 'contact.id',
    ]);
});

服务器配置

根目录权限

根目录权限截图

Apache虚拟主机配置(/etc/httpd/conf/httpd.conf)

<VirtualHost *:443>
 ServerAdmin user@domain.com
 ServerName myapp.com
 ServerAlias www.myapp.com
 DocumentRoot /var/www/html/public
 <Directory /var/www/html>
  Options Indexes FollowSymLinks MultiViews
  AllowOverride All
  Require all granted
 </Directory>
</VirtualHost>

<VirtualHost *:80>
  ServerAdmin user@domain.com
  ServerName myapp.com
  ServerAlias www.myapp.com
  DocumentRoot /var/www/html/public
  <Directory /var/www/html>
    Options Indexes FollowSymLinks MultiViews
    AllowOverride All
    Require all granted
  </Directory>
</VirtualHost>

IAM策略

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:ListBucket",
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::mybucket",
                "arn:aws:s3:::mybucket/*"
            ]
        }
    ]
}

错误截图

HTTP 403错误截图


进一步排查步骤

  1. 检查Apache mod_security模块

    • 执行httpd -M | grep security确认是否启用mod_security
    • 若启用,临时修改/etc/httpd/conf.d/mod_security.conf,将SecRuleEngine On改为SecRuleEngine Off,重启Apache:systemctl restart httpd,测试上传是否恢复正常
    • 若正常,需调整mod_security规则,允许合法文件上传请求,或针对上传路由添加排除规则
  2. 验证PHP临时上传目录权限

    • 检查默认临时目录/tmp权限:ls -ld /tmp,确保Apache运行用户(通常为apache)有读写权限
    • 若权限不足,执行chmod 1777 /tmp修复,或在php.ini中指定自定义临时目录并配置正确权限
  3. 检查SELinux状态

    • 执行sestatus查看SELinux状态,若为enforcing模式,临时设置为permissive测试:setenforce 0
    • 若上传正常,需配置SELinux规则允许Apache操作:
      • 允许Laravel存储目录写入:chcon -R -t httpd_sys_rw_content_t /var/www/html/storage
      • 允许临时目录访问:chcon -t httpd_tmp_t /tmp
  4. 查看Apache访问日志

    • 检查/var/log/httpd/access_log中对应403请求的记录,查看是否有mod_security拦截标记或其他错误说明
  5. 测试极简上传接口

    • 创建独立测试路由,排除Laravel验证逻辑干扰:
      Route::post('/test-upload', function(Request $request) {
          if($request->hasFile('avatar')) {
              return response()->json(['success' => true, 'filename' => $request->file('avatar')->getClientOriginalName()]);
          }
          return response()->json(['success' => false]);
      });
      
    • 用Postman或curl发送请求,判断是服务器层面还是Laravel层面的问题
  6. 验证post_max_size配置

    • 检查php.ini中的post_max_size值,需大于upload_max_filesize,否则会导致大文件请求被拦截

内容的提问来源于stack exchange,提问作者farjam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 05:30:53