Deploying a Laravel Application on cPanel (Step-by-Step Guide)
Laravel on shared hosting used to be "don't". In 2026, with PHP 8.2 on LiteSpeed and a well-configured cPanel account, it is a perfectly reasonable choice for sites up to a few thousand daily visitors. This guide shows the exact steps from local build to live site, the common pitfalls that trip people up, and when you should stop trying to force Laravel onto shared hosting and move to a VPS.
What you need before you start
- A cPanel hosting account (our cPanel Starter plan and above both work). If you are on DirectAdmin, the steps are almost identical — substitute "DirectAdmin" wherever you see "cPanel".
- PHP 8.1 or higher for Laravel 10; PHP 8.2+ for Laravel 11 and 12. Verify via cPanel → Select PHP Version.
- The following PHP extensions enabled (most are on by default): openssl, pdo, mbstring, tokenizer, xml, ctype, json, bcmath, fileinfo, curl.
- A MySQL database + user created from cPanel → MySQL Databases. See our Creating a MySQL Database guide.
- Your domain pointed to the hosting account (domain + nameservers set up).
Two deployment paths
Your hosting plan decides which path you use:
| Path | When to use | Effort |
|---|---|---|
| A — Build locally, upload artifact | Shared plans without SSH / Composer | Low, one-time |
| B — SSH + Composer on server | Plans with SSH access (our cPanel Pro and above) | Cleaner, automatable |
Path A is universal. Path B is preferred if you have SSH.
Path A: Build locally, upload artifact
Step 1 — build the production artifact
On your dev machine, inside your Laravel project:
composer install --no-dev --optimize-autoloader
npm run build # Vite / Laravel Mix
php artisan config:cache
php artisan route:cache
php artisan view:cacheStep 2 — create the upload zip
Zip the project but exclude these paths:
.git/node_modules/.env(never upload your dev .env)tests/- Contents of
storage/logs/andbootstrap/cache/(keep the folders, clear contents)
Step 3 — upload via cPanel File Manager
- Upload the zip to
/home/YOURUSER/laravel-app/(outsidepublic_html, deliberately). - Use "Extract" in File Manager.
- Move only the contents of
laravel-app/public/intopublic_html/. - Edit
public_html/index.php. Change the tworequire __DIR__.'/../...'lines so they point to../laravel-app/bootstrap/app.phpand../laravel-app/vendor/autoload.php.
This keeps every sensitive file (.env, Eloquent models, routes) outside the publicly reachable document root. Only the public/ directory is exposed.
Step 4 — set permissions
storage/ 755 (or 775 if your server needs group-write)
bootstrap/cache/ 755Never chmod to 777 on shared hosting. It is a common shortcut that creates a real security risk.
Step 5 — create your production .env
Create a fresh .env in /home/YOURUSER/laravel-app/ (not in public_html/). Set at minimum:
APP_ENV=production
APP_DEBUG=false
APP_URL=https://yourdomain.com
DB_CONNECTION=mysql
DB_HOST=localhost
DB_DATABASE=yourcpaneluser_dbname
DB_USERNAME=yourcpaneluser_dbuser
DB_PASSWORD=yourdbpasswordThen generate the app key:
php artisan key:generate(Run from cPanel's Terminal if your plan has it, or via SSH.)
Step 6 — migrate and link storage
php artisan migrate --force
php artisan storage:link--force is required in APP_ENV=production to suppress the confirmation prompt.
Path B: SSH + Composer on server
If your plan includes SSH (cPanel Pro and above), this is much cleaner.
ssh [email protected]
cd ~
git clone https://github.com/you/yourapp.git laravel-app
cd laravel-app
composer install --no-dev --optimize-autoloader
cp .env.example .env
nano .env # fill in production values
php artisan key:generate
php artisan migrate --force
php artisan storage:linkThen in cPanel → Domains, set the document root for your domain to /home/yourcpaneluser/laravel-app/public/. Or symlink:
rm -rf ~/public_html
ln -s ~/laravel-app/public ~/public_htmlCommon problems and their fixes
500 Internal Server Error
Check the logs first:
~/laravel-app/storage/logs/laravel.log— Laravel errors- cPanel → Errors — PHP errors
The top three causes are: .env missing or unreadable, cached config stale (php artisan config:clear), or wrong permissions on storage/.
"No application encryption key has been specified"
You forgot step 5. Run:
php artisan key:generateMigrations fail with "Specified key was too long; max key length is 1000 bytes"
This is a long-standing Laravel issue with older MySQL versions + utf8mb4. In app/Providers/AppServiceProvider.php, inside the boot() method, add:
use Illuminate\Support\Facades\Schema;
public function boot(): void
{
Schema::defaultStringLength(191);
}Then re-run migrations.
Directory listing (Index of /) instead of the app
The document root is pointing to the wrong folder, or index.php is missing. Also disable directory listing by adding to public_html/.htaccess:
Options -IndexesMail sending fails
PHP's native mail() has very poor deliverability on shared hosting. Use SMTP instead. Configure in .env:
MAIL_MAILER=smtp
MAIL_HOST=mail.yourdomain.com
MAIL_PORT=465
[email protected]
MAIL_PASSWORD=yourmailpassword
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS="[email protected]"For higher volume, use SendGrid, Mailgun, or AWS SES.
Post-deploy hardening
Force HTTPS
Install Let's Encrypt SSL via cPanel → SSL/TLS Status → Run AutoSSL. Then force HTTPS in App\Providers\AppServiceProvider::boot():
if (config('app.env') === 'production') {
\URL::forceScheme('https');
}Run the Laravel scheduler
In cPanel → Cron Jobs, add:
* * * * * cd /home/yourcpaneluser/laravel-app && php artisan schedule:run >> /dev/null 2>&1Disable APP_DEBUG
APP_DEBUG=false is not just recommended — it prevents stack traces from leaking to attackers, and it gives a noticeable performance boost.
Verify .env is unreachable from the web
Open https://yourdomain.com/.env in a browser. You should see a 404. If you see your config, your document root is set wrong — fix immediately.
When to stop and upgrade to a VPS
You have outgrown shared hosting when you see any of:
- Regular response times above 800ms (check via Core Web Vitals)
- More than ~5,000 pageviews a day
- Frequent "resource limit exceeded" notices from cPanel
- You need Redis, queues, or long-lived background workers (see Background Jobs & Queues)
At that point our VPS hosting gives you direct control over PHP-FPM, Redis, nginx, and process managers like PM2 or Supervisord.
Frequently asked questions
Can I run Laravel on the cheapest cPanel plan?
Yes, Path A works on any PHP 8.1+ plan. You lose access to composer install on the server, which means you must build locally and upload. Not a blocker, just slightly less convenient.
Does Composer work on DirectAdmin?
Yes, provided SSH is enabled. Our DirectAdmin plans with SSH behave the same as cPanel with SSH for this workflow.
How do I update Laravel after the first deploy?
Path A: rebuild locally, upload the new zip, extract and replace (or move new files in). Path B: git pull, composer install --no-dev --optimize-autoloader, php artisan migrate --force, php artisan config:cache. Also run php artisan queue:restart if you are using queues.
Why is my Laravel site showing a blank white page?
Almost always APP_DEBUG=false masking a fatal error. Temporarily set APP_DEBUG=true, refresh, read the error, fix it, set back to false. Alternatively read storage/logs/laravel.log.
Can I use Redis on shared hosting?
No. Redis needs a running daemon, which shared hosting does not expose. Fall back to CACHE_DRIVER=file and SESSION_DRIVER=file in .env. If your app actually needs Redis, it is time to move to a VPS.
Is Laravel Forge better than cPanel for Laravel?
Forge is excellent, but it provisions servers on DigitalOcean / AWS / Linode — you are paying for both Forge and the VPS. On our VPS plans you can replicate the Forge setup manually with nginx + PHP-FPM + Supervisord. Forge buys convenience; DIY buys cost savings. Choose based on team size.
Reach us at [email protected] if you hit anything this guide does not cover. Happy deploying.