Agency PM — cPanel Installation Guide

Complete step-by-step guide to deploying Laravel API + React frontend on cPanel shared hosting

🗄 Laravel 11 Backend ⚡ React + Vite Frontend 🗃 MySQL Database 🌐 cPanel / Apache

Table of contents

  1. Prerequisites & hosting requirements
  2. Create the MySQL database
  3. Set up subdomains
  4. Upload & configure the Laravel backend
  5. Configure the .env file
  6. Install dependencies with Composer
  7. Run migrations & seed admin user
  8. Configure .htaccess files
  9. Build & upload the React frontend
  10. Configure CORS
  11. Test the installation
  12. Troubleshooting
1

Prerequisites & hosting requirements

Before starting, confirm your cPanel hosting plan supports the following:

RequirementMinimumWhere to check
PHP version8.2 or highercPanel → Software → Select PHP Version
MySQL5.7 or 8.0cPanel → Databases → MySQL Databases
SSH / Terminal accessRequired for ComposercPanel → Advanced → Terminal
Composer2.xAvailable via SSH: composer --version
Node.js18+ (local machine only)Build React on your PC, upload the dist/ folder
Disk space500 MB minimumcPanel → Files → Disk Usage
No SSH access? Some budget cPanel hosts don't offer SSH. In that case, run composer install on your local machine targeting PHP 8.2, then upload the vendor/ folder via FTP. See Step 6 for details.

Required PHP extensions

Go to cPanel → Select PHP Version → Extensions and enable:

pdo_mysql    # Database driver
mbstring     # String handling
openssl      # Encryption / JWT
tokenizer    # Laravel requirement
xml          # XML parsing
ctype        # Character type functions
fileinfo     # File validation
json         # JSON support
bcmath       # Precise arithmetic
2

Create the MySQL database

  1. Open MySQL Databases In cPanel, go to Databases → MySQL Databases
  2. Create a new database Enter a name, e.g. yourusername_agencypm → click Create Database
  3. Create a database user Scroll down to MySQL Users → enter username and a strong password → click Create User
  4. Add user to database In the Add User to Database section, select the user and database → click Add → grant ALL PRIVILEGES → click Make Changes
Write down your database name, username, and password — you'll need them in Step 5 when configuring the .env file.
3

Set up subdomains

The recommended setup uses two subdomains — one for the API and one for the frontend:

SubdomainPurposeDocument root
api.yourdomain.com Laravel backend API public_html/api/public
app.yourdomain.com React frontend public_html/app
  1. Go to Subdomains cPanel → Domains → Subdomains
  2. Create API subdomain Subdomain: api · Domain: yourdomain.com · Document root: public_html/api/public → click Create
  3. Create app subdomain Subdomain: app · Domain: yourdomain.com · Document root: public_html/app → click Create
  4. Add SSL certificates cPanel → Security → SSL/TLS → Run AutoSSL on both subdomains for HTTPS
Important: The Laravel document root must point to the public/ folder, NOT the project root. If you point to the project root, your .env file and application code will be publicly accessible.
4

Upload & configure the Laravel backend

Folder structure on server

public_html/ ├── api/ <-- Laravel project root goes here │ ├── app/ │ ├── bootstrap/ │ ├── config/ │ ├── database/ │ ├── public/ <-- subdomain points HERE │ │ ├── index.php │ │ └── .htaccess │ ├── routes/ │ ├── storage/ │ ├── .env │ └── composer.json └── app/ <-- React build files go here ├── index.html ├── .htaccess └── assets/

Upload via File Manager

  1. Open File Manager cPanel → Files → File Manager → navigate to public_html/
  2. Create the api folder Click + Folder → name it api
  3. Zip your backend folder On your local machine, zip the entire backend/ folder contents (not the folder itself)
  4. Upload the zip Inside public_html/api/ → click Upload → upload your zip file
  5. Extract it Right-click the zip → Extract → confirm destination is /public_html/api/
FTP alternative: Use FileZilla or any FTP client with your cPanel FTP credentials to upload files directly. Host: yourdomain.com, Port: 21, credentials from cPanel → FTP Accounts.
5

Configure the .env file

  1. Copy the example file In File Manager, navigate to public_html/api/ → find .env.example → right-click → Copy → name it .env
  2. Edit the .env file Right-click .envEdit → update all values below
# Application
APP_NAME="Agency PM"
APP_ENV=production
APP_KEY=                        # Will generate this in Step 7
APP_DEBUG=false
APP_URL=https://api.yourdomain.com

# Database — use values from Step 2
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=yourusername_agencypm
DB_USERNAME=yourusername_dbuser
DB_PASSWORD=YourStrongPassword123!

# Frontend URL (for CORS)
FRONTEND_URL=https://app.yourdomain.com
SANCTUM_STATEFUL_DOMAINS=app.yourdomain.com

# Session & Cache
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_CONNECTION=sync
Never set APP_DEBUG=true in production. This exposes your database credentials, file paths, and application secrets to anyone who triggers an error.
6

Install dependencies with Composer

Option A — Via SSH (recommended)

Open cPanel → Advanced → Terminal (or connect via SSH with your cPanel credentials):

# Navigate to your project
cd ~/public_html/api

# Install PHP dependencies (production mode)
composer install --no-dev --optimize-autoloader

# Generate app key
php artisan key:generate

# Set storage permissions
chmod -R 775 storage bootstrap/cache
chown -R $(whoami):$(whoami) storage bootstrap/cache

Option B — Without SSH (local vendor upload)

If your host does not provide terminal access:

  1. On your local machine Open terminal in the backend/ folder → run: composer install --no-dev --optimize-autoloader
  2. Generate the app key locally Run: php artisan key:generate --show → copy the output (e.g. base64:Abc123...)
  3. Paste the key into .env on server Edit the .env file on the server → set APP_KEY=base64:Abc123...
  4. Upload the vendor/ folder Upload the entire vendor/ folder to public_html/api/vendor/ via FTP or File Manager
The vendor/ folder is large (~50–100MB). Uploading via File Manager as a ZIP is faster than uploading thousands of individual files via FTP. Zip it locally, upload, then extract on the server.
7

Run migrations & create admin user

Via SSH / cPanel Terminal

# Run all database migrations
php artisan migrate --force

# Open Laravel Tinker to create your admin account
php artisan tinker

Inside Tinker, paste and run:

App\Models\User::create([
    'name'     => 'Admin',
    'email'    => 'admin@youragency.com',
    'password' => bcrypt('YourStrongPassword!'),
    'role'     => 'super_admin',
]);

# Press Ctrl+D or type exit() to leave Tinker

Alternative — phpMyAdmin (no SSH)

If you have no SSH access, import migrations manually:

  1. Open phpMyAdmin cPanel → Databases → phpMyAdmin → select your database
  2. Create tables manually Go to the SQL tab and run the CREATE TABLE statements from the migration files in database/migrations/
  3. Insert admin user In the SQL tab, run the INSERT statement below
-- Run in phpMyAdmin SQL tab
INSERT INTO users (name, email, password, role, is_active, created_at, updated_at)
VALUES (
  'Admin',
  'admin@youragency.com',
  '$2y$12$paste_bcrypt_hash_here',   -- generate at bcrypt-generator.com
  'super_admin',
  1,
  NOW(), NOW()
);
8

Configure .htaccess files

Backend — public_html/api/public/.htaccess

Laravel includes this file by default. Verify it contains:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^(.*)$ index.php [QSA,L]

    # Handle Authorization header (required for Sanctum tokens)
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
</IfModule>

# Security headers
<IfModule mod_headers.c>
    Header always set X-Content-Type-Options nosniff
    Header always set X-Frame-Options DENY
    Header always set X-XSS-Protection "1; mode=block"
</IfModule>

Frontend — public_html/app/.htaccess

Create this file so React Router works correctly (all routes serve index.html):

Options -MultiViews
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /

    # Serve existing files and folders directly
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d

    # Everything else goes to React's index.html
    RewriteRule ^ index.html [QSA,L]
</IfModule>

# Cache static assets
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType text/css "access plus 1 month"
    ExpiresByType application/javascript "access plus 1 month"
</IfModule>
9

Build & upload the React frontend

On your local machine

# Navigate to frontend folder
cd frontend

# Create production environment file
echo 'VITE_API_URL=https://api.yourdomain.com/api/v1' > .env.production

# Install dependencies
npm install

# Build for production
npm run build

# The dist/ folder is ready to upload
ls dist/

Upload to server

  1. Zip the dist/ folder contents Select everything inside dist/ (not the folder itself) → zip it
  2. Upload to public_html/app/ In File Manager, navigate to public_html/app/ → Upload the zip file
  3. Extract the zip Right-click the zip → Extract → confirm public_html/app/ as destination
  4. Create the .htaccess file In public_html/app/ → New File → name it .htaccess → paste the contents from Step 8
After uploading, public_html/app/ should contain: index.html, .htaccess, and an assets/ folder with your JS/CSS bundles.
10

Configure CORS

Laravel must allow requests from your React frontend domain. Open public_html/api/config/cors.php and update:

<?php

return [
    'paths'             => ['api/*'],
    'allowed_methods'   => ['*'],
    'allowed_origins'   => [
        'https://app.yourdomain.com',
    ],
    'allowed_origins_patterns' => [],
    'allowed_headers'   => ['*'],
    'exposed_headers'   => [],
    'max_age'           => 0,
    'supports_credentials' => false,
];

Also ensure Sanctum is configured in config/sanctum.php:

'stateful' => explode(',', env(
    'SANCTUM_STATEFUL_DOMAINS',
    'app.yourdomain.com'
)),

Clear the config cache after making changes:

php artisan config:clear
php artisan cache:clear
11

Test the installation

Test the API

Open your browser or use a tool like Postman:

# Should return {"message":"Unauthenticated."} — means API is working
GET https://api.yourdomain.com/api/v1/auth/me

# Test login endpoint
POST https://api.yourdomain.com/api/v1/auth/login
Content-Type: application/json

{
  "email": "admin@youragency.com",
  "password": "YourStrongPassword!"
}

Checklist before going live

CheckExpected resultStatus
https://api.yourdomain.com/api/v1/auth/loginReturns JSON with access_tokenTest this
https://app.yourdomain.comLogin page loadsTest this
Login with admin accountRedirects to dashboardTest this
Create a projectProject appears in listTest this
Add a team memberMember visible in Team pageTest this
Open kanban boardDrag & drop worksTest this
12

Troubleshooting

ProblemLikely causeFix
500 Internal Server Error on API Missing APP_KEY or wrong .env Run php artisan key:generate; check storage/logs/laravel.log
404 Not Found on API routes .htaccess not working / mod_rewrite disabled Contact host to enable mod_rewrite; verify public/.htaccess exists
CORS error in browser console Frontend URL not in allowed_origins Update config/cors.php; run php artisan config:clear
React app shows blank page React Router needs .htaccess fallback Create .htaccess in app folder (see Step 8)
Cannot connect to database Wrong DB credentials or DB_HOST Use 127.0.0.1 not localhost; double-check username format (cpanelusername_dbname)
Composer not found via SSH Composer not in PATH Try php /usr/local/bin/composer install or download composer.phar manually
Storage permission error Wrong file permissions Run chmod -R 775 storage bootstrap/cache
Login works but token rejected SANCTUM_STATEFUL_DOMAINS mismatch Set SANCTUM_STATEFUL_DOMAINS=app.yourdomain.com in .env (no https://)
PHP version error during composer install Server using PHP < 8.2 cPanel → Software → Select PHP Version → switch to PHP 8.2

Check Laravel error logs

# Via SSH
tail -f ~/public_html/api/storage/logs/laravel.log

# Via File Manager
# Navigate to: public_html/api/storage/logs/laravel.log → right-click → View
All done! Your Agency PM system is live. Share https://app.yourdomain.com with your team. Create client accounts from the Team page — clients will see only their own portal.