最常见原因是public/storage软链接缺失或损坏,需执行php artisan storage:link重建;同时须确保storage/app/public目录权限正确、selinux未拦截、且文件存于public磁盘而非local磁盘。

public/storage 软链接缺失或损坏
上传的图片明明存在 storage/app/public/ 里,但通过 URL 访问却 404,最常见原因是 public/storage 这个软链接没建、被删了,或者指向错误。Laravel 默认不把 storage 目录暴露给 Web,必须靠 php artisan storage:link 在 public/ 下创建符号链接,才能让 Nginx/Apache 正确路由到真实文件。
实操建议:
- 先确认链接是否存在:
ls -l public/storage—— 如果报错或显示 “No such file”,就说明没了 - 删掉残留的坏链接(如果有):
rm -f public/storage - 重新生成:
php artisan storage:link - 检查权限:生成后运行
ls -ld public/storage,输出应类似lrwxrwxrwx,不是drwxr-xr-x(那是目录,不是链接)
storage/app/public 目录写入权限不足
即使软链接存在,如果 storage/app/public/ 目录本身不可写,上传操作就会失败——你可能看到 file_put_contents(): Failed to open stream: Permission denied 或直接静默失败(尤其在生产环境关闭错误报告时)。
实操建议:
- 确认 Web 服务器用户(如
www-data、nginx或apache)对以下路径有写权限:storage/app/public/和storage/framework/(日志和缓存也依赖它) - 临时修复(开发环境):
sudo chmod -R 775 storage/+sudo chown -R $USER:www-data storage/ - 生产环境更稳妥:
sudo setfacl -R -m u:www-data:rwx storage/(需启用 ACL),避免过度开放 777 - 别只改
public/子目录——整个storage/都要可写,否则storage:link可能因无法写入storage/framework/cache而卡住
SELinux 或安全模块拦截(Linux 生产服务器常见)
CentOS/RHEL 系统上,即使权限看着全对,上传仍失败或访问 404,大概率是 SELinux 在拦。它会阻止 Web 进程读取 storage/ 下的文件,哪怕 ls -l 显示权限没问题。
实操建议:
- 快速验证:
sudo sestatus查看是否为enforcing - 临时放行(测试用):
sudo setsebool -P httpd_read_user_content 1 - 永久标记目录上下文:
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/your-app/storage(/.*)?",再执行sudo restorecon -Rv /var/www/your-app/storage - 注意:不要关 SELinux(
setenforce 0),那只是掩盖问题
上传逻辑误用 local disk 写到非公开路径
很多人用 Storage::disk('local')->put(...),结果文件进了 storage/app/(Web 不可访问),而不是 storage/app/public/。这时即使权限全开,URL 也永远 404——因为根本不在公开路径下。
实操建议:
- 明确区分用途:
publicdisk 对应storage/app/public/,且必须配合storage:link;localdisk 默认就是storage/app/,纯后端用 - 正确写法:
Storage::disk('public')->put('avatars/user.jpg', $file),然后访问/storage/avatars/user.jpg - 别手动拼路径:
file_put_contents(storage_path('app/public/xxx.jpg'), ...)容易出错,绕过 Laravel 的磁盘抽象,丢失事件、适配器等能力
public/storage 很容易被 Git 忽略或手动删除,而错误日志又常被静默吞掉,导致排查绕远路。











