Cara Menjalankan Perintah Artisan dan Membuat Storage Link Laravel di cPanel Tanpa SSH

Saat mengunggah project Laravel ke shared hosting atau cPanel, terkadang pengguna tidak mendapatkan akses SSH atau Terminal. Akibatnya, beberapa perintah penting Laravel tidak bisa dijalankan secara langsung, seperti:

php artisan storage:link
php artisan config:clear
php artisan cache:clear
php artisan config:cache
php artisan key:generate

Sebagai alternatif, kita dapat membuat file PHP sementara yang menjalankan perintah Artisan melalui browser. Kita juga dapat membuat symbolic link secara manual menggunakan fungsi symlink() dari PHP.

Artikel ini membahas cara:

  1. Membuat symbolic link folder storage secara manual.

  2. Menjalankan perintah Artisan Laravel melalui browser.

  3. Menghapus symbolic link atau folder public/storage yang lama.

  4. Membuat ulang storage link Laravel.

  5. Menghindari risiko keamanan ketika menjalankan script melalui browser.


Mengapa Laravel Membutuhkan Storage Link?

Secara default, file publik Laravel disimpan di:

storage/app/public

Namun folder tersebut tidak dapat diakses langsung dari browser. Agar file dapat dibuka melalui URL, Laravel membuat symbolic link dari:

public/storage

menuju:

storage/app/public

Dengan demikian, file yang disimpan menggunakan disk public dapat diakses melalui URL seperti:

https://domainsaya.com/storage/nama-file.jpg

Perintah standar Laravel untuk membuat link tersebut adalah:

php artisan storage:link

Jika hosting tidak menyediakan SSH atau Terminal, kita dapat membuat link tersebut menggunakan file PHP.


Struktur Folder Project

Pada contoh ini, struktur folder hosting adalah:

/home/username/domainsaya.com/
├── laravelapp/
│   ├── app/
│   ├── bootstrap/
│   ├── config/
│   ├── public/
│   ├── storage/
│   ├── vendor/
│   └── artisan
│
└── artisan-run.php

Folder project Laravel berada di:

/home/username/domainsaya.com/laravelapp

Pastikan document root domain sudah diarahkan ke folder:

/home/username/domainsaya.com/laravelapp/public

Pengaturan document root biasanya dapat dilakukan melalui menu Domains di cPanel.


Metode 1: Membuat Symbolic Link secara Manual

Buat file PHP sementara, misalnya:

storage-link.php

Letakkan file tersebut di folder yang dapat dibuka melalui browser.

Gunakan kode berikut:

<?php

declare(strict_types=1);

$target = '/home/username/domainsaya.com/laravelapp/storage/app/public';

$link = '/home/username/domainsaya.com/laravelapp/public/storage';

header('Content-Type: text/plain; charset=UTF-8');

echo "Target: {$target}\n";
echo "Link: {$link}\n\n";

if (!is_dir($target)) {
    exit(
        "GAGAL: Folder target tidak ditemukan.\n" .
        "Periksa kembali lokasi storage/app/public.\n"
    );
}

if (is_link($link)) {
    exit(
        "Symbolic link storage sudah tersedia.\n" .
        "Mengarah ke: " . readlink($link) . "\n"
    );
}

if (file_exists($link)) {
    exit(
        "GAGAL: Sudah ada folder atau file pada lokasi berikut:\n" .
        "{$link}\n\n" .
        "Hapus atau ubah nama folder storage tersebut terlebih dahulu."
    );
}

if (!function_exists('symlink')) {
    exit(
        "GAGAL: Fungsi symlink() tidak tersedia.\n" .
        "Kemungkinan fungsi tersebut dinonaktifkan oleh penyedia hosting."
    );
}

if (symlink($target, $link)) {
    echo "BERHASIL membuat symbolic link:\n\n";
    echo "{$link}\n";
    echo "-> {$target}\n";
} else {
    $error = error_get_last();

    echo "GAGAL membuat symbolic link.\n";

    if ($error !== null) {
        echo "Error: {$error['message']}\n";
    }

    echo "\nKemungkinan pembuatan symbolic link diblokir oleh hosting.";
}

Penjelasan Variabel Target dan Link

Variabel $target merupakan folder asli tempat file publik Laravel disimpan:

$target = '/home/username/domainsaya.com/laravelapp/storage/app/public';

Variabel $link adalah lokasi symbolic link yang akan dibuat:

$link = '/home/username/domainsaya.com/laravelapp/public/storage';

Hasil akhirnya adalah:

laravelapp/public/storage
->
laravelapp/storage/app/public

Jangan menggunakan folder root domain sebagai nilai $link, seperti:

$link = '/home/username/domainsaya.com/';

Folder tersebut sudah tersedia sehingga file_exists() pasti menghasilkan nilai true. Selain itu, symbolic link harus memiliki nama tujuan, yaitu storage.


Menjalankan Script Storage Link

Setelah file diunggah, buka melalui browser:

https://domainsaya.com/storage-link.php

Jika berhasil, hasilnya kurang lebih seperti berikut:

BERHASIL membuat symbolic link:

/home/username/project/public/storage
->
/home/username/project/storage/app/public

Kemudian lakukan pengujian dengan membuka file yang berada di dalam storage/app/public.

Contoh:

https://domainsaya.com/storage/contoh.jpg

Metode 2: Menjalankan Artisan Laravel Melalui Browser

Selain membuat symbolic link secara manual, kita juga dapat memanggil Laravel Console Kernel agar perintah Artisan dapat dijalankan melalui PHP.

Buat file:

artisan-run.php

Letakkan di folder satu tingkat di atas project Laravel atau sesuaikan nilai $projectPath.

Gunakan kode berikut:

<?php

declare(strict_types=1);

ob_start();

use Illuminate\Contracts\Console\Kernel;

$projectPath = __DIR__ . '/laravelapp';

require $projectPath . '/vendor/autoload.php';

$app = require_once $projectPath . '/bootstrap/app.php';

$kernel = $app->make(Kernel::class);

$publicStorage = $projectPath . '/public/storage';

/**
 * Menghapus file, symbolic link, atau folder beserta seluruh isinya.
 */
function removeStoragePath(string $path): bool
{
    if (is_link($path) || is_file($path)) {
        return unlink($path);
    }

    if (!is_dir($path)) {
        return true;
    }

    $items = scandir($path);

    if ($items === false) {
        return false;
    }

    foreach ($items as $item) {
        if ($item === '.' || $item === '..') {
            continue;
        }

        $itemPath = $path . DIRECTORY_SEPARATOR . $item;

        if (is_link($itemPath) || is_file($itemPath)) {
            if (!unlink($itemPath)) {
                return false;
            }

            continue;
        }

        if (is_dir($itemPath) && !removeStoragePath($itemPath)) {
            return false;
        }
    }

    return rmdir($path);
}

$output = [];

$output[] = 'Project Laravel: ' . $projectPath;
$output[] = 'Public storage: ' . $publicStorage;
$output[] = '';

try {
    /*
    |--------------------------------------------------------------------------
    | Hapus public/storage lama
    |--------------------------------------------------------------------------
    */

    if (is_link($publicStorage)) {
        if (!unlink($publicStorage)) {
            throw new RuntimeException(
                'Gagal menghapus symbolic link storage lama.'
            );
        }

        $output[] = 'Symbolic link storage lama berhasil dihapus.';
    } elseif (is_dir($publicStorage)) {
        if (!removeStoragePath($publicStorage)) {
            throw new RuntimeException(
                'Gagal menghapus folder public/storage lama.'
            );
        }

        $output[] = 'Folder public/storage lama berhasil dihapus.';
    } elseif (file_exists($publicStorage)) {
        if (!unlink($publicStorage)) {
            throw new RuntimeException(
                'Gagal menghapus file public/storage lama.'
            );
        }

        $output[] = 'File public/storage lama berhasil dihapus.';
    } else {
        $output[] = 'Public storage lama tidak ditemukan.';
    }

    $output[] = '';

    /*
    |--------------------------------------------------------------------------
    | Jalankan perintah Artisan
    |--------------------------------------------------------------------------
    */

    $commands = [
        'config:clear',
        'storage:link',
        'cache:clear',
        'config:cache',
    ];

    foreach ($commands as $command) {
        $output[] = 'Menjalankan: php artisan ' . $command;

        try {
            $exitCode = $kernel->call($command);
            $commandOutput = trim($kernel->output());

            if ($commandOutput !== '') {
                $output[] = $commandOutput;
            }

            $output[] = 'Exit code: ' . $exitCode;
        } catch (Throwable $exception) {
            $output[] = 'ERROR: ' . $exception->getMessage();
        }

        $output[] = '';
    }
} catch (Throwable $exception) {
    $output[] = 'ERROR UTAMA: ' . $exception->getMessage();
}

ob_clean();

header('Content-Type: text/plain; charset=UTF-8');

echo implode(PHP_EOL, $output);

Perintah Artisan yang Dijalankan

Script tersebut menjalankan beberapa perintah berikut.

Menghapus Cache Konfigurasi

php artisan config:clear

Perintah ini menghapus cache konfigurasi Laravel agar perubahan pada file .env dapat dibaca ulang.

Membuat Storage Link

php artisan storage:link

Perintah ini membuat symbolic link:

public/storage
->
storage/app/public

Menghapus Cache Aplikasi

php artisan cache:clear

Perintah ini menghapus cache aplikasi Laravel.

Membuat Ulang Cache Konfigurasi

php artisan config:cache

Perintah ini menyimpan konfigurasi Laravel dalam bentuk cache agar aplikasi berjalan lebih efisien pada lingkungan production.


Cara Menggunakan Artisan Runner

1. Periksa Lokasi Project

Pastikan baris berikut sesuai dengan nama folder project Laravel:

$projectPath = __DIR__ . '/laravelapp';

Jika nama folder Laravel adalah aplikasi, ubah menjadi:

$projectPath = __DIR__ . '/aplikasi';

2. Upload File ke Hosting

Upload artisan-run.php ke folder yang sama dengan folder project.

Contoh:

/home/username/domainsaya.com/artisan-run.php
/home/username/domainsaya.com/laravelapp/

3. Buka melalui Browser

Buka URL file tersebut:

https://domainsaya.com/artisan-run.php

Script akan:

  1. Memeriksa lokasi project.

  2. Menghapus public/storage lama.

  3. Menjalankan config:clear.

  4. Menjalankan storage:link.

  5. Menjalankan cache:clear.

  6. Menjalankan config:cache.

  7. Menampilkan hasil setiap perintah.

4. Periksa Exit Code

Apabila berhasil, biasanya setiap perintah akan menampilkan:

Exit code: 0

Exit code 0 menunjukkan bahwa perintah berhasil dijalankan.


Cara Membuat APP_KEY Tanpa SSH

Untuk instalasi Laravel baru yang belum memiliki APP_KEY, tambahkan perintah berikut ke dalam array $commands:

'key:generate --force',

Contohnya:

$commands = [
    'key:generate --force',
    'config:clear',
    'storage:link',
    'cache:clear',
    'config:cache',
];

Namun perintah tersebut hanya boleh dijalankan untuk instalasi baru.

Setelah APP_KEY berhasil dibuat, segera hapus perintah:

'key:generate --force',

Jangan menjalankan key:generate --force berulang kali pada aplikasi yang sudah aktif.

Mengganti APP_KEY dapat menyebabkan:

  • Session pengguna tidak dapat dibaca.

  • Cookie terenkripsi menjadi tidak valid.

  • Data terenkripsi Laravel tidak dapat dibuka.

  • Pengguna otomatis logout.

  • Fitur reset password atau token tertentu bermasalah.

  • Data yang disimpan menggunakan Crypt tidak dapat dipulihkan menggunakan key baru.


Mengatur APP_URL Laravel

Buka file .env, lalu atur APP_URL sesuai domain aplikasi:

APP_URL=https://domainsaya.com

Pastikan tidak menggunakan slash tambahan di akhir URL:

APP_URL=https://domainsaya.com/

Walaupun sebagian besar konfigurasi tetap berjalan, format yang lebih konsisten adalah:

APP_URL=https://domainsaya.com

Setelah mengubah .env, jalankan:

php artisan config:clear
php artisan config:cache

Mengatasi Error Cache Database

Pada beberapa project Laravel, perintah berikut dapat menampilkan error database:

php artisan cache:clear

Hal ini biasanya terjadi karena cache menggunakan driver database, tetapi tabel cache belum tersedia atau koneksi database belum benar.

Periksa konfigurasi .env:

CACHE_STORE=file
SESSION_DRIVER=file

Pada project Laravel versi lama, konfigurasi cache mungkin menggunakan:

CACHE_DRIVER=file
SESSION_DRIVER=file

Kemudian jalankan ulang:

php artisan config:clear
php artisan cache:clear
php artisan config:cache

Jika project memang menggunakan database sebagai tempat penyimpanan cache, pastikan tabel cache sudah dibuat:

php artisan cache:table
php artisan migrate

Karena artikel ini membahas hosting tanpa SSH, perintah tersebut juga dapat dimasukkan sementara ke dalam array $commands.

Contoh:

$commands = [
    'config:clear',
    'cache:table',
    'migrate --force',
    'cache:clear',
    'config:cache',
];

Jalankan migrasi hanya jika sudah memahami perubahan database yang akan dilakukan.


Error Symbolic Link Sudah Tersedia

Jika muncul pesan:

The [public/storage] link already exists

berarti sudah terdapat file, folder, atau symbolic link di:

public/storage

Script Artisan Runner di atas akan mencoba menghapus lokasi tersebut sebelum menjalankan:

php artisan storage:link

Jika ingin menghapus symbolic link secara manual melalui PHP, gunakan:

<?php

$storagePath = __DIR__ . '/laravelapp/public/storage';

if (is_link($storagePath)) {
    if (unlink($storagePath)) {
        echo 'Symbolic link berhasil dihapus.';
    } else {
        echo 'Symbolic link gagal dihapus.';
    }
} else {
    echo 'Lokasi tersebut bukan symbolic link.';
}

Jangan menghapus folder berikut:

storage/app/public

Folder tersebut adalah lokasi asli file upload Laravel.

Yang boleh dihapus untuk dibuat ulang adalah:

public/storage

Error Fungsi symlink Dinonaktifkan

Jika muncul pesan seperti:

Call to undefined function symlink()

atau:

symlink() has been disabled for security reasons

kemungkinan penyedia hosting menonaktifkan fungsi symlink().

Beberapa solusi yang dapat dilakukan:

  1. Gunakan menu Terminal cPanel jika tersedia.

  2. Hubungi penyedia hosting dan minta dibuatkan symbolic link.

  3. Minta penyedia hosting mengaktifkan fungsi symlink().

  4. Gunakan perintah Artisan melalui Laravel Console Kernel.

  5. Ubah konfigurasi penyimpanan apabila hosting benar-benar tidak mendukung symbolic link.

Pada shared hosting tertentu, symbolic link juga dapat dibatasi menggunakan konfigurasi open_basedir.


Error Cannot Modify Header Information

Error berikut dapat muncul ketika Laravel mencoba mengirim header setelah script lebih dahulu mencetak output:

Cannot modify header information - headers already sent

Karena itu, script Artisan Runner menggunakan:

ob_start();

Kemudian sebelum mencetak hasil akhir, buffer dibersihkan menggunakan:

ob_clean();

Pastikan tidak ada:

  • Spasi sebelum <?php.

  • Teks HTML sebelum kode PHP.

  • Tanda backtick Markdown.

  • Penutup kode seperti tiga tanda backtick.

  • Karakter tersembunyi sebelum tag PHP.

  • Lebih dari satu tag <?php yang tidak diperlukan.

Keamanan dan Disclaimer

Script pada artikel ini menjalankan perintah Laravel melalui URL publik. Oleh karena itu, penggunaannya memiliki risiko keamanan apabila file dibiarkan berada di hosting.

Setelah perintah berhasil dijalankan, segera hapus file berikut:

artisan-run.php
storage-link.php

Jangan membiarkan file tersebut dapat diakses oleh orang lain.

Orang yang mengetahui URL file dapat menjalankan ulang perintah dan berpotensi:

  • Menghapus symbolic link storage.

  • Menghapus folder public/storage.

  • Menghapus cache aplikasi.

  • Mengubah konfigurasi aplikasi.

  • Mengganti APP_KEY apabila perintah tersebut masih tersedia.

  • Menyebabkan aplikasi mengalami error atau downtime.

Sebelum menjalankan script, lakukan backup terhadap:

  • File .env.

  • Database aplikasi.

  • Folder storage/app/public.

  • Konfigurasi domain.

  • Folder project Laravel.

Kode dalam artikel ini harus disesuaikan dengan struktur folder masing-masing hosting. Jangan menyalin path contoh tanpa memeriksa lokasi project melalui File Manager cPanel.

Penulis tidak bertanggung jawab atas kehilangan file, kerusakan aplikasi, perubahan data, error konfigurasi, atau gangguan layanan akibat kesalahan penggunaan script.


Rekomendasi Penggunaan yang Lebih Aman

Tambahkan token rahasia agar file tidak dapat dijalankan sembarang orang.

Contoh:

<?php

declare(strict_types=1);

$secretToken = 'ganti-dengan-token-rahasia-yang-panjang';

$providedToken = $_GET['token'] ?? '';

if (!hash_equals($secretToken, $providedToken)) {
    http_response_code(403);
    exit('Akses ditolak.');
}

Kemudian buka file menggunakan URL:

https://domainsaya.com/artisan-run.php?token=ganti-dengan-token-rahasia-yang-panjang

Walaupun sudah menggunakan token, file tetap harus segera dihapus setelah selesai digunakan.


Kesimpulan

Pada hosting cPanel tanpa SSH, perintah Artisan Laravel tetap dapat dijalankan dengan memanggil Console Kernel melalui file PHP sementara.

Untuk membuat file upload Laravel dapat diakses dari browser, pastikan symbolic link berikut tersedia:

public/storage
->
storage/app/public

Gunakan file storage-link.php apabila hanya ingin membuat symbolic link. Gunakan artisan-run.php apabila ingin menjalankan beberapa perintah Artisan sekaligus.

Hal terpenting yang harus diperhatikan adalah:

  • Sesuaikan path project dengan struktur hosting.

  • Jangan menghapus storage/app/public.

  • Hanya hapus public/storage ketika ingin membuat ulang link.

  • Jangan menjalankan key:generate --force pada aplikasi aktif.

  • Backup aplikasi sebelum menjalankan script.

  • Hapus seluruh file runner setelah proses selesai.

Komentar