Storage Link Laravel Tidak Jalan di Server

Storage link Laravel tidak jalan di server biasanya karena php artisan storage:link belum dijalankan, document root Nginx tidak mengarah ke folder public, symlink tidak didukung hosting, permission folder salah, atau file disimpan ke disk yang berbeda. Jawaban cepatnya: cek public/storage, jalankan ulang storage:link, pastikan file ada di storage/app/public, lalu cek permission dan konfigurasi web server.

Masalah ini sering muncul setelah fitur upload berhasil di lokal, tetapi gambar menjadi 404 di production. Kodenya terlihat benar, file ada di server, namun URL /storage/... tetap tidak bisa dibuka.

Secara default, file publik Laravel disimpan di:

storage/app/public

Browser tidak bisa mengakses folder itu langsung. Agar file publik bisa dibuka dari web, Laravel membuat symbolic link:

public/storage -> storage/app/public

Perintahnya:

php artisan storage:link

Setelah link dibuat, file seperti ini:

storage/app/public/produk/kopi.jpg

bisa diakses lewat:

https://tokolaravel.com/storage/produk/kopi.jpg

Contoh Upload yang Benar

Controller:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ProdukController extends Controller
{
    public function store(Request $request)
    {
        $validated = $request->validate([
            'foto' => ['required', 'image', 'max:2048'],
        ]);

        $path = $request->file('foto')->store('produk', 'public');

        // Simpan $path ke database, contoh: produk/kopi.jpg
        // Produk::create(['foto' => $path]);

        return back()->with('status', 'Foto berhasil diupload.');
    }
}

Blade:

<img src="{{ asset('storage/' . $produk->foto) }}" alt="{{ $produk->nama }}">

Bagian pentingnya adalah store('produk', 'public'). Jika disk public tidak dipakai, file bisa tersimpan di tempat yang tidak terhubung ke /storage.

Checklist Diagnosis Cepat

Masuk ke folder project di server:

cd /var/www/toko-laravel

Cek apakah symlink ada:

ls -la public | grep storage

Output yang sehat biasanya mirip:

storage -> /var/www/toko-laravel/storage/app/public

Cek file upload benar-benar ada:

find storage/app/public -maxdepth 3 -type f | head

Jalankan ulang link jika belum ada:

php artisan storage:link

Jika link sudah ada tetapi salah, hapus link-nya lalu buat ulang:

rm public/storage
php artisan storage:link

Hati-hati: rm public/storage aman jika public/storage adalah symlink. Jangan jalankan rm -rf public/storage tanpa mengecek, karena bisa menghapus folder sungguhan jika konfigurasi server berbeda.

Cek Konfigurasi Filesystem

Di .env, pastikan disk default sesuai kebutuhan:

FILESYSTEM_DISK=public

Lalu cek config/filesystems.php. Disk public umumnya mengarah ke storage/app/public dan punya URL /storage.

'public' => [
    'driver' => 'local',
    'root' => storage_path('app/public'),
    'url' => env('APP_URL') . '/storage',
    'visibility' => 'public',
],

Jika kamu baru mengubah .env atau config, bersihkan cache:

php artisan config:clear
php artisan config:cache

Cek Nginx dan Document Root

Nginx harus mengarah ke folder public:

root /var/www/toko-laravel/public;

Jika root mengarah ke folder utama project, struktur URL bisa kacau dan file internal berisiko terekspos.

Untuk Laravel standar, konfigurasi try_files tetap diperlukan:

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

Setelah mengubah Nginx:

sudo nginx -t
sudo systemctl reload nginx

Cek Permission Folder

Laravel butuh permission menulis ke storage:

sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R ug+rw storage bootstrap/cache

Jika upload dilakukan oleh PHP-FPM user www-data, folder storage/app/public juga harus bisa ditulis oleh user itu.

Cek user PHP-FPM:

ps aux | grep php-fpm

Di beberapa server, user-nya bisa nginx, www-data, atau user deploy khusus. Sesuaikan perintah chown dengan server kamu.

Beberapa shared hosting membatasi symbolic link. Jika php artisan storage:link gagal atau link tidak bisa dibaca web server, opsi yang lebih aman adalah:

  1. gunakan VPS yang mendukung symlink,
  2. tanyakan ke provider hosting apakah symlink diizinkan,
  3. simpan file publik langsung di disk yang memang diarahkan ke public path,
  4. gunakan object storage seperti S3-compatible storage untuk project yang lebih serius.

Untuk pemula yang belajar production, VPS biasanya lebih mudah diprediksi daripada shared hosting yang banyak batasannya.

Kesalahan yang Sering Terjadi

Menyimpan File Tanpa Disk public

Kode ini menyimpan ke disk default:

$path = $request->file('foto')->store('produk');

Jika disk default bukan public, file tidak akan muncul di URL /storage.

Gunakan:

$path = $request->file('foto')->store('produk', 'public');

Menyimpan Path Lengkap ke Database

Jangan simpan URL penuh jika tidak perlu:

https://tokolaravel.com/storage/produk/kopi.jpg

Lebih fleksibel simpan path relatif:

produk/kopi.jpg

Lalu bentuk URL di Blade:

{{ asset('storage/' . $produk->foto) }}

Saat clone project baru di server, symlink tidak otomatis ada. Masukkan ini ke checklist deploy:

php artisan storage:link

Permission Diselesaikan dengan chmod 777

chmod 777 terlihat cepat, tetapi terlalu longgar. Lebih baik benahi owner dan group folder.

APP_URL Masih Localhost

Jika URL file dibuat dari config dan APP_URL masih localhost, link bisa mengarah ke domain salah. Perbaiki .env production.

Pencegahan

  1. Selalu gunakan disk public untuk file yang memang boleh diakses browser.
  2. Simpan path relatif di database.
  3. Jalankan php artisan storage:link saat deploy awal.
  4. Pastikan Nginx root mengarah ke folder public.
  5. Atur permission storage dan bootstrap/cache.
  6. Jangan memakai chmod 777 sebagai solusi permanen.
  7. Masukkan upload file ke checklist smoke test setelah deploy.

Bacaan Terkait

Referensi Resmi

FAQ

Kenapa gambar upload 404 di server?

Biasanya karena public/storage belum ada, file tidak disimpan ke disk public, Nginx root salah, atau permission folder tidak cocok.

Tidak selalu. Biasanya cukup saat deploy pertama atau saat symlink hilang. Tetapi aman memasukkannya ke checklist deploy karena perintah ini cepat memberi tahu jika link sudah ada.

Bolehkah folder storage dijadikan public root?

Jangan. Laravel hanya mengekspos bagian yang memang publik lewat public/storage. Folder storage juga berisi log, cache, dan file lain yang tidak untuk browser.

Apa bedanya Storage::url() dan asset('storage/...')?

Storage::url($path) membuat URL berdasarkan konfigurasi disk. asset('storage/' . $path) membuat URL dari path public. Untuk project sederhana dengan disk public, keduanya bisa dipakai asal path-nya konsisten.

Bagaimana kalau file ada tetapi tetap tidak bisa dibuka?

Cek permission file, owner folder, root Nginx, dan apakah public/storage benar-benar symlink ke storage/app/public.