Never put the whole project in the web root. Only the contents of public/ must be web-accessible. Your .env being downloadable is a serious leak: rotate APP_KEY, DB passwords and any API keys afterwards.
Recommended layout
/home/user/laravel-app/ <- everything except public/
/home/user/public_html/ <- contents of public/
Steps
- Check PHP: Laravel 12 needs PHP 8.2 or newer. Select it in cPanel "MultiPHP Manager" and enable
mbstring, openssl, pdo_mysql, fileinfo, ctype, xml, tokenizer.
- Run
composer install --no-dev --optimize-autoloader locally (or on the server if you have a terminal) and upload the vendor folder, since many shared hosts have no Composer.
- Upload the app to
laravel-app/, and copy public/* into public_html/.
- Edit
public_html/index.php to point to the new paths:
require __DIR__.'/../laravel-app/vendor/autoload.php';
$app = require_once __DIR__.'/../laravel-app/bootstrap/app.php';
Also change the maintenance file path if present.
- Create
.env in laravel-app/ with APP_ENV=production, APP_DEBUG=false, a generated APP_KEY (run php artisan key:generate --show locally and paste), and the DB credentials from cPanel.
- Import the database (phpMyAdmin) or run migrations if you have SSH:
php artisan migrate --force.
- Make
storage/ and bootstrap/cache/ writable (755 or 775, owned by your user).
- Storage link: if you cannot run
php artisan storage:link, create the symlink with a one-off route/PHP script, or point FILESYSTEM_DISK public root to a folder inside public_html.
Pitfalls
- 500 error with a blank page: check
storage/logs/laravel.log, then PHP version and missing extensions.
- Run
php artisan config:cache only after .env is final.
- The scheduler needs a cPanel cron job (see the scheduling topic in this section).
The .env generator can help you draft a production .env.