Deploying a Laravel application on cPanel shared hosting
12 min read · 30-Oct-2025
villagehosting.in team
30 October 2025
Laravel is not designed for shared hosting, but it can be made to work. Here is the exact process, with the common errors you will hit and how to fix them.
Queue workers and schedulers don't work reliably on shared hosting
Laravel's queue workers (php artisan queue:work) are persistent background processes — shared hosting doesn't support these. If your Laravel app uses queues for emails, notifications, or background jobs, you need a VPS. Similarly, the Laravel scheduler requires a cron job running every minute; shared hosting typically only allows cron every 15 minutes minimum.
Should you use shared hosting for Laravel?
Shared hosting works for Laravel if:
- You need PHP 8.1+, MySQL, and Composer — standard on modern cPanel hosts
- Your application is low-to-medium traffic
- You do not need Redis, Supervisor, or Node.js (VPS or above)
For production Laravel apps with queues, websockets, or significant traffic, VPS or managed hosting is the right choice. Shared hosting is fine for staging, small client sites, and low-traffic apps.
Prerequisites
Before deploying:
- Your cPanel host must have PHP 8.1 or 8.2
- Composer must be available (most modern cPanel hosts have it)
- SSH access is strongly recommended (most cPanel hosts provide it)
- Check that
extension=pdo_mysql,extension=mbstring,extension=xml, andextension=bcmathare enabled via cPanel → MultiPHP INI Editor
Step 1: Upload your Laravel application
You have two options:
Option A: Upload via SSH + Git (recommended)
SSH into your server:
ssh username@yourdomain.com
Go to the directory above public_html:
cd ~/
Clone your repository:
git clone https://github.com/yourusername/your-laravel-app.git myapp
Install Composer dependencies:
cd ~/myapp
composer install --optimize-autoloader --no-dev
Option B: Upload via FTP/File Manager
- Build your app locally:
composer install --optimize-autoloader --no-dev - Compress the entire project (excluding
.git,node_modules) - Upload to a directory in your home folder — not inside
public_html - Extract via File Manager
Step 2: Set the document root correctly
This is the most important step. Laravel's public/ directory should be your document root, not the project root. Putting the project root in public_html exposes your .env file to the web.
Correct structure:
~/myapp/ ← Laravel project root (private)
~/myapp/public/ ← This should be the document root
~/public_html/ ← Currently pointing here by default
Method A: Use cPanel's "Document Root" setting (cleanest)
If your domain has its own cPanel account or you can set the document root:
- cPanel → Domains → Manage → Document Root
- Set it to
/home/username/myapp/public
Method B: Symlink public_html to Laravel's public
If you cannot change the document root:
# Rename or empty public_html first
mv public_html public_html_old
ln -s ~/myapp/public ~/public_html
Method C: Move files into public_html (least preferred)
If you cannot do either of the above, copy public/ contents into public_html/ and update index.php to point to the correct paths:
Edit public_html/index.php:
require __DIR__.'/../myapp/vendor/autoload.php';
$app = require_once __DIR__.'/../myapp/bootstrap/app.php';
Step 3: Configure .env
Copy the example file:
cp ~/myapp/.env.example ~/myapp/.env
Edit .env with your real values:
APP_NAME="My App"
APP_ENV=production
APP_KEY= # will generate this next
APP_DEBUG=false
APP_URL=https://yourdomain.com
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=yourusername_dbname
DB_USERNAME=yourusername_dbuser
DB_PASSWORD=your_db_password
MAIL_MAILER=smtp
MAIL_HOST=mail.yourdomain.com
MAIL_PORT=587
MAIL_USERNAME=your@email.com
MAIL_PASSWORD=your_email_password
Create the database and user in cPanel → MySQL Databases first.
Step 4: Generate application key
cd ~/myapp
php artisan key:generate
This fills in APP_KEY in your .env. Without this, Laravel cannot encrypt sessions or cookies.
Step 5: Set storage permissions
chmod -R 775 ~/myapp/storage
chmod -R 775 ~/myapp/bootstrap/cache
Or if you need the web server user to own these:
chown -R nobody:nobody ~/myapp/storage
chown -R nobody:nobody ~/myapp/bootstrap/cache
The web server user varies — it is nobody on most cPanel setups, or check cPanel → PHP Environment.
Step 6: Run migrations
cd ~/myapp
php artisan migrate --force
The --force flag is required in production (Laravel asks for confirmation interactively otherwise).
Step 7: Cache configuration for production
php artisan config:cache
php artisan route:cache
php artisan view:cache
These significantly improve performance by pre-compiling your configuration, routes, and views.
If you ever change .env, run php artisan config:clear first, then re-cache.
Step 8: Set up cron for scheduled tasks
If your app uses Laravel Scheduler:
- cPanel → Cron Jobs
- Set frequency: every minute
- Command:
cd /home/username/myapp && php artisan schedule:run >> /dev/null 2>&1
Replace username with your actual cPanel username and myapp with your project directory name.
Common errors and fixes
"No application encryption key has been specified": Run php artisan key:generate
"Class not found" errors: Run composer dump-autoload
404 on all routes except homepage: .htaccess in your document root is missing or malformed. The public/.htaccess from a fresh Laravel install handles this — make sure it is present and that mod_rewrite is enabled.
"SQLSTATE: Access denied": Your .env DB credentials are wrong or the database user does not have privileges. Check in cPanel → MySQL Databases.
Sessions not persisting: Storage permissions are wrong — the web server cannot write to storage/framework/sessions. Fix with chmod -R 775 storage/.
"Whoops, looks like something went wrong": Set APP_DEBUG=true temporarily to see the actual error. Never leave this on in production.
Laravel queues on shared hosting
Queue workers (php artisan queue:work) are not possible on shared hosting because they need a persistent process. Options:
Sync driver (simplest, no worker needed):
QUEUE_CONNECTION=sync
Jobs run synchronously. Fine for low-volume queues where job latency is acceptable.
Database driver + cron polling (limited but functional):
QUEUE_CONNECTION=database
Add a cron job to run php artisan queue:work --once every minute. This processes one job per minute — not suitable for high-volume queues but works for low-frequency background tasks.
For real queue processing, upgrade to VPS with Supervisor.
Deploying updates
After pushing new code:
cd ~/myapp
git pull
composer install --optimize-autoloader --no-dev
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
Add this as a deploy script and run it after each deployment.