Deploying Laravel on Shared Hosting (No SSH Required)
Source: Dev.to

Why Laravel Deployment on Shared Hosting Is Different
1. Project File Structure
A typical Laravel project looks like this:
project/
├── app/
├── bootstrap/
├── config/
├── public/
├── storage/
├── vendor/
└── artisan
However, shared hosting usually exposes only one public directory:
├── public_html/
2. No SSH, No Commands
If there is no SSH access, you cannot:
- Install Composer dependencies
- Build assets (
npm run …) - Run migrations
- Clear or optimize caches
How to Solve the Situation – Step‑by‑Step
Step 1: Prepare Your Application Locally
Clone your Laravel project into a production‑ready copy so you don’t affect your development setup:
├── laravel_app/
├── laravel_app_prod/
Inside laravel_app_prod, prepare the application for production:
# clearing the cache
php artisan config:clear
php artisan cache:clear
php artisan route:clear
php artisan view:clear
# installing production dependencies and optimizing
composer install --no-dev --optimize-autoloader
# building frontend assets
npm run build
Step 2 – Re‑organize Project File Structure
To match the shared‑hosting layout, move only the contents of the public/ folder into the new public_html/ folder:
public_html/
handleRequest(Illuminate\Http\Request::capture());
Important: Incorrect paths will cause a 500 error.
Step 4 – Set Up the Database Using cPanel
- Open MySQL Database Wizard in cPanel.
- Create a database.
- Create a database user and assign ALL PRIVILEGES.
Then update the .env file inside laravel_app_prod:
APP_NAME=MyApp
APP_ENV=production
APP_DEBUG=false # very important (security)
APP_URL=https://your-app-domain
DB_DATABASE=database_name
DB_USERNAME=database_user
DB_PASSWORD=database_password
⚠️ Never enable
APP_DEBUG=truein production.
Step 5 – Temporary Deploy Route (Token Protected)
Because there is no SSH, you can temporarily execute Artisan commands via a protected web route. Remove this route immediately after deployment.
-
Add a secret token to
.env:DEPLOY_TOKEN=verySecretRandomToken123 -
Add the route in
routes/web.php:use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Route; Route::get('/deploy/{token}', function ($token) { abort_unless($token === env('DEPLOY_TOKEN'), 403); // 1. Run migrations & clear cache Artisan::call('migrate', ['--force' => true]); Artisan::call('optimize:clear'); // 2. Fix storage link (custom fix) // We point to the 'public_html' folder using $_SERVER['DOCUMENT_ROOT'] $targetFolder = storage_path('app/public'); $linkFolder = $_SERVER['DOCUMENT_ROOT'] . '/storage'; if (!file_exists($linkFolder)) { symlink($targetFolder, $linkFolder); $storageStatus = 'Storage link created successfully.'; } else { $storageStatus = 'Storage link already exists.'; } return "Deployment completed.\nMigrations run.\nCache cleared.\n" . $storageStatus; });
Step 6 – Upload Files to the Server
Using cPanel File Manager:
- Zip
laravel_app_prod/. - Zip the contents of
public/. - Upload both archives:
- Extract
laravel_app_prodoutsidepublic_html. - Extract the public files inside
public_html.
- Extract
Why place laravel_app_prod outside public_html?
This keeps your core application code out of the web‑accessible directory, providing an essential security layer.
Step 7 — Set Critical Permissions
Laravel needs write access to a few folders. If they aren’t set correctly, logs won’t write and sessions won’t be saved.
- In cPanel File Manager, navigate to
laravel_app_prod/storage. - Right‑click → Change Permissions.
- Set the permissions to 775 (User: Read/Write/Execute, Group: Read/Write/Execute, World: Read/Execute).
- Repeat the same steps for
laravel_app_prod/bootstrap/cache.
⚠️ Note: Never set these folders to
777. That is a major security risk on shared hosting.
Step 8 — Run Migrations and Remove the Deploy Route
Open the following URL in a browser (replace your-domain.com with your actual domain):
https://your-domain.com/deploy/verySecretRandomToken123
If everything is correct you’ll see:
Deployment completed successfully
Immediately after that:
- ❗ Delete the deploy route.
- ❗ Remove
DEPLOY_TOKENfrom.env.
These steps are critical for security.
Common Issues Faced
500 Internal Server Error
- Incorrect file paths in
index.php. - Wrong PHP version (use cPanel MultiPHP Manager).
- Incorrect folder permissions (
storage775,bootstrap/cache775).
403 Unauthorized
- Unmatched route token with
DEPLOY_TOKEN.
Final Thoughts
Shared hosting isn’t ideal for Laravel, but it’s still common for:
- Client projects
- MVPs
- Budget‑constrained deployments
With proper preparation and a secure workflow, Laravel applications can run reliably without SSH access.
💡 Pro Tip – Safer Database Alternative
If you’re uncomfortable running migrations via a web route (which carries security risks), use the Export/Import method:
- Run migrations on your local machine.
- Export the local database as an
.sqlfile. - In cPanel → phpMyAdmin on the server, import the
.sqlfile directly.
This avoids executable logic in production routes.
🔗 Stay Connected
Follow me for more Laravel tutorials, dev tips, deployment workflows, and real‑world production solutions:
- LinkedIn:
- Medium: (and join my mailing list: )
Found this article useful?
🙏 Show your support by clapping 👏, subscribing 🔔, and sharing on social networks.