Laravel is the most common PHP framework we see customers try to move onto shared or reseller cPanel hosting, and it's also the one that trips people up fastest. The framework wasn't built with "upload via FTP and go" in mind — it expects a specific document root, a writable storage directory, and a way to run scheduled artisan commands. None of that is obvious from a stock cPanel account. Here's how to get a Laravel app running cleanly on cPanel without fighting it for a weekend.
Symptom: 500 Errors, Blank Pages, or "Composer Not Found"
You upload your Laravel project the way you would any PHP site, point the domain at the account's root, and instead of your app you get one of these:
- A 500 Internal Server Error with nothing useful in the browser
- A blank white page (Laravel's own error page failed to render)
- Your site showing a directory listing of
app/,routes/, andvendor/instead of your app composer: command not foundwhen you SSH in and try to install dependencies- "419 Page Expired" the moment you submit a form
Each of these traces back to a different piece of Laravel's setup that a generic PHP hosting flow doesn't handle automatically.
Cause: Laravel Expects Things cPanel Doesn't Give You by Default
Four gaps cause almost every deployment headache:
- Wrong document root. Laravel's entry point is
public/index.php, not the project root. If your domain points at the folder containingapp/,vendor/, andpublic/side by side, visitors either see a directory listing or hit files that were never meant to be public. - No Composer, or the wrong PHP version. Shared hosting doesn't always have Composer pre-installed, and even when it does, it may default to a PHP version older than what your
composer.jsonrequires. - Missing or wrong
.envvalues. Laravel refuses to boot cleanly without a validAPP_KEY, and it'll throw a database connection error if the.envstill has your local dev credentials instead of the cPanel MySQL database. - Storage and cache directories aren't writable. Laravel writes logs, compiled views, and session files to
storage/andbootstrap/cache/. If cPanel's file ownership doesn't match what PHP-FPM runs as, every request fails silently or with a 500.
Fix: Step-by-Step Deployment
1. Confirm SSH access and PHP version
Laravel deployment is dramatically easier with SSH. In cPanel, check Advanced > Terminal or Security > SSH Access — if it's available, use it. If you're on a plan without SSH, you'll need to run Composer locally and upload the vendor/ folder instead (slower, but it works).
Set the correct PHP version first, under Software > MultiPHP Manager. Check your composer.json for the "php" constraint under require and match it — Laravel 10 wants PHP 8.1+, Laravel 11 wants 8.2+.
2. Upload the project outside the public web root
Don't put the whole Laravel project inside public_html. Instead:
- Create a folder above it, e.g.
/home/username/laravel-app/ - Upload or clone your project there
- Only the contents of the project's
public/folder should ever be reachable from the web
3. Point the document root at public/
How you do this depends on whether the app lives on the primary domain or an addon domain:
- Addon domain or subdomain: In cPanel's Domains section, when adding the domain, set the document root directly to
laravel-app/publicinstead of the defaultpublic_html/domainfolder. - Primary domain: If you can't change the account's primary document root, create a symlink instead — from SSH, run
ln -s /home/username/laravel-app/public /home/username/public_htmlafter backing up or clearing whatever's currently inpublic_html.
Either way, visiting the domain should now load public/index.php, not a folder listing.
4. Install dependencies with Composer
SSH into the account and run:
cd ~/laravel-app
composer install --no-dev --optimize-autoloader
If composer isn't found, check whether your host provides it as composer1/composer2, or ask support to enable it — most cPanel servers running CloudLinux have it available system-wide even if it's not on the default PATH for your shell.
--no-dev matters here: it skips testing and debugging packages you don't want on a production server, and it avoids dependency conflicts some dev-only packages have with production PHP versions.
5. Set up the .env file
Copy .env.example to .env and fill in real values. Create a MySQL database and user first under Databases > MySQL Database Wizard in cPanel, then update:
APP_ENV=production
APP_DEBUG=false
APP_URL=https://yourdomain.com
DB_CONNECTION=mysql
DB_HOST=localhost
DB_DATABASE=cpaneluser_dbname
DB_USERNAME=cpaneluser_dbuser
DB_PASSWORD=your-actual-password
Then generate the application key — Laravel won't encrypt sessions or cookies correctly without it:
php artisan key:generate
Set APP_DEBUG=false before you go live. Leaving it true on a production site prints full stack traces — including database credentials and file paths — to anyone who triggers an error.
6. Fix storage and cache permissions
These two directories need to be writable by the web server user:
chmod -R 775 storage bootstrap/cache
If that's not enough (some cPanel/CloudLinux setups run PHP-FPM as your account user via suPHP-style isolation, others don't), check what user owns Apache/Nginx's worker process and match ownership with chown instead of loosening permissions further. Avoid chmod -R 777 — it's a common "fix" that also makes those directories writable by anything else on a shared server.
7. Run migrations
php artisan migrate --force
The --force flag is required in production mode — Laravel blocks migrations in a non-local environment unless you explicitly confirm it. No SSH? Export your local database and import the .sql file through phpMyAdmin instead, matching table names and structure exactly.
8. Schedule the cron job for artisan schedule:run
If your app uses Laravel's task scheduler (queued jobs, scheduled emails, cleanup tasks), it does nothing until something calls it. In cPanel, go to Advanced > Cron Jobs and add:
* * * * * cd /home/username/laravel-app && php artisan schedule:run >> /dev/null 2>&1
This runs every minute, which is what Laravel expects — the scheduler itself decides which tasks are actually due, so it's not wasteful even at that frequency.
Fixing the "419 Page Expired" Error
This usually means session or cookie handling is broken, not that your form is doing anything wrong. Check three things: APP_URL in .env matches your actual domain and protocol (https, not http), SESSION_DOMAIN isn't set to something that doesn't match your live domain, and storage/framework/sessions is writable. If you're behind Cloudflare with SSL set to "Flexible," switch it to "Full" — otherwise Laravel sees the connection as plain HTTP and cookies flagged secure never get set.
Prevention
- Never commit
.envto version control, and never leaveAPP_DEBUG=trueon a live site. - Keep a deployment checklist (or a small shell script) so
composer install --no-dev,artisan migrate --force, and cache-clearing commands run the same way every time — manual deploys are where steps get skipped. - Run
php artisan config:cacheandphp artisan route:cacheafter deploying to production — it noticeably speeds up cold requests on shared hosting where every millisecond of PHP boot time counts. - Take a database and file backup before every migration.
artisan migrateis usually safe, but a bad migration on a shared MySQL instance is much easier to prevent than to undo.
If you're not sure whether your SkyServer plan supports the PHP version, Composer access, or SSH your Laravel app needs, that's worth confirming with support before you start uploading — it saves a lot of back-and-forth compared to finding out mid-deploy.
Frequently Asked Questions
Does shared cPanel hosting support Laravel?
Yes, as long as the plan includes SSH access (or you're comfortable uploading a pre-built vendor/ folder) and a PHP version matching your Laravel release. VPS and reseller plans give you more control over document root and cron, which makes the setup smoother.
Can I deploy Laravel without SSH access?
Yes, but it's more manual. Run composer install on your local machine, then upload the entire project — including the vendor/ folder — via FTP or File Manager. You'll still need to set the document root to public/ and configure .env by editing it directly in File Manager.
Why does my site show a list of files instead of my app?
The domain's document root is pointed at the project root instead of the public/ subfolder. Fix it in cPanel's Domains settings, or create a symlink from public_html to your project's public/ directory.
How do I run Laravel's queue worker on cPanel?
Shared hosting generally doesn't allow long-running background processes like php artisan queue:work. Use the database or sync queue driver with a cron job calling queue:work --stop-when-empty every minute instead, or move queue-heavy apps to a VPS where you can run a persistent worker under Supervisor.
What PHP version does my Laravel app need?
Check the "php" line under require in composer.json. As a rough guide, Laravel 9 needs PHP 8.0+, Laravel 10 needs 8.1+, and Laravel 11 needs 8.2+. Set the matching version in MultiPHP Manager before running Composer, or dependency resolution will fail.
