For teams deploying Laravel apps to shared hosting using Git push, post-receive hooks, and manual cPanel setups, this SOP enables:

  • Clean deployment via Git
  • MySQL database configuration
  • Environment setup via .env
  • Seamless handling of subdomains vs. root domains
  • Production-ready deployment without root access

📦 Assumptions

  • Shared hosting (e.g., A2, Hostinger, Bluehost) with cPanel + SSH
  • Git, Composer, and PHP (>= 7.4) are available
  • Laravel version 8+
  • DNS already configured to hosting server

🔐 SSH Key Setup (One-Time)

  1. Generate SSH key on your local machine:
ssh-keygen -t rsa -b 4096 -C "[email protected]"
  1. Copy the public key:
cat ~/.ssh/id_rsa.pub
  1. SSH into the server and append it to ~/.ssh/authorized_keys:
nano ~/.ssh/authorized_keys
# Paste the key and save

🔧 Laravel Deployment Structure

❗ Subdomain vs. Primary Domain: Key Differences

Context Laravel Folder Placement Document Root Target Notes
Subdomain Full Laravel app (including /public) /subdomain.domain.com/public Clean separation; recommended for staging/UAT
Primary Domain Laravel core outside /public_html /public_html/ Must expose only public/, rest stays outside

🌐 Subdomain Deployment (Recommended)

🔹 Step-by-Step

  1. Create Subdomain in cPanel

    • Navigate to Domains > Subdomains
    • Example: uat.domain.com
    • Set Document Root: /home//subdomains/uat.domain.com/public
  2. Create Bare Git Repo on Server

mkdir -p /home//repos/laravel-uat.git
cd /home//repos/laravel-uat.git
git init --bare
  1. Configure post-receive Hook
nano hooks/post-receive
#!/bin/sh
GIT_WORK_TREE=/home//subdomains/uat.domain.com git checkout -f
cd /home//subdomains/uat.domain.com
composer install --no-dev
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
chmod +x hooks/post-receive
  1. Deploy from Local or GitHub
git remote add live ssh://@/home//repos/laravel-uat.git
git push live uat
git push live master

🏠 Primary Domain Deployment (domain.com)

❗ Caveat

Shared hosting ties domain.com to /public_html. You cannot place the full Laravel folder in /public_html. Use the approach below:

🔹 Directory Structure

/home//laravel_main
/home//public_html    ← contains only files from `/public`

🔹 Steps

  1. Push Full Laravel App to ~/laravel_main

  2. Copy /public Contents to /public_html

cp -R laravel_main/public/* public_html/
  1. Edit public_html/index.php

Update paths:

require __DIR__.'/../laravel_main/vendor/autoload.php';
$app = require_once __DIR__.'/../laravel_main/bootstrap/app.php';
  1. Install Dependencies & Migrate
cd ~/laravel_main
composer install --no-dev
php artisan migrate --force
php artisan key:generate
php artisan config:cache
  1. Set Permissions
chmod -R 775 storage
chmod -R 775 bootstrap/cache

🛠️ MySQL Database Setup

  1. Go to cPanel > MySQL Databases
  2. Create database: db_laravel
  3. Create user: user_laravel
  4. Assign user to database with All Privileges
  5. Update .env:
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=db_laravel
DB_USERNAME=user_laravel
DB_PASSWORD=StrongPassword123

⚠️ Common Issues & Fixes

Problem Cause Fix
500 Internal Server Error Wrong index paths / file permissions Fix index.php, run chmod -R 775 storage bootstrap/cache
Composer not installed Shared host restriction Install locally and upload vendor/
.env missing Laravel config errors Upload .env, run php artisan config:cache
Directory listing visible Wrong document root Set root to public/, not project root
Artisan fails PHP CLI outdated Check php -v, request upgrade or use compatible Laravel version

📌 Summary Table

Context Code Location Public Files Location Method
Subdomain ~/subdomains/app/ ~/subdomains/app/public Full Laravel folder used
Primary Domain ~/laravel_main ~/public_html public folder only exposed

✅ Post-Deployment Checklist

  • [ ] .env configured correctly
  • [ ] php artisan key:generate run
  • [ ] storage and bootstrap/cache are writable
  • [ ] Composer dependencies installed
  • [ ] Artisan commands run: config:cache, route:cache, view:cache
  • [ ] Database connection tested
  • [ ] Subdomain or root domain resolving properly
  • [ ] Debug mode off in production (APP_DEBUG=false)

P.S. - This post was refined with the help of ChatGPT and includes an image sourced from the web. If anyone has concerns or would like the content removed, I’m open to doing so.