Migrating a Production Laravel App from cPanel to a VPS: The Zero-Downtime Field Runbook
A step-by-step engineering runbook for moving a live custom Laravel stack from shared cPanel hosting to a standalone Linux VPS without dropping database transactions or business email.
Moving a custom-built Laravel application from shared cPanel hosting to a standalone Linux VPS (such as Hostinger VPS, Hetzner, or DigitalOcean) is a rite of passage for growing platforms. Shared hosting is cost-effective during initial development, but aggressive resource throttling, hardcoded PHP memory ceilings, and sluggish I/O will eventually force a migration.
However, moving a production application is fraught with operational risk. Done carelessly, you risk leaking sensitive .env credentials, corrupting relational database transactions mid-migration, or breaking commercial email deliverability when DNS records switch over.
This runbook outlines the exact sequence I use to execute zero-downtime cutovers from shared cPanel environments to standalone VPS instances.
The Battlefield Scenario
A client operates a custom Laravel application on shared cPanel hosting. The stack comprises:
- Laravel 10/11 application core with local storage assets.
- MySQL relational database with transactional client records.
- Cron-based background job execution (
php artisan schedule:run). - Business email accounts running through the primary domain’s MX records.
The objective: move the entire stack to a fresh Ubuntu VPS with zero data loss, minimal user disruption (sub-5 minutes of maintenance mode), and absolute security hygiene.
Phase 1: Pre-Migration Safeguards (24 Hours Prior)
The single biggest blunder during web migrations is touching files before preparing DNS. If your DNS TTL (Time to Live) is set to 86,400 seconds (24 hours), resolving servers across the globe will cache your old shared hosting IP for a full day after you cut over—leading to split-brain database states where half your customers write to the old server and half to the new.
1. Lowering the DNS TTL
Log into your DNS zone editor (Cloudflare, cPanel, or domain registrar) exactly 24 hours prior to the planned cutover:
- Reduce the TTL on the root domain (
@),www, and sub-domains to 300 seconds (5 minutes). - Record all existing MX, SPF (
v=spf1 ...), DKIM, and DMARC TXT records into an offline text dossier.
# Verify the current TTL from your local terminal
$ dig +nocmd +noall +answer targetdomain.com
targetdomain.com. 300 IN A 198.51.100.45
Phase 2: VPS Environment Preparation
Before transferring application code, the target VPS environment must be configured to mirror or improve upon the shared hosting specifications.
1. Setting up PHP 8.2/8.3 & Essential Extensions
On the fresh Ubuntu VPS:
sudo apt update && sudo apt install -y nginx mysql-server \
php8.2-fpm php8.2-mysql php8.2-mbstring php8.2-xml \
php8.2-bcmath php8.2-curl php8.2-zip unzip git
2. The Document Root Security Trap
In cPanel, developers often struggle with document roots and resort to bad practices—like moving index.php out of /public into the project root. Never do this on a VPS.
Your Nginx virtual host configuration must point strictly to Laravel’s /public directory. Pointing to the root directory exposes your .env file, database credentials, and Git commit history to the open web:
# /etc/nginx/sites-available/targetdomain.com
server {
listen 80;
server_name targetdomain.com www.targetdomain.com;
root /var/www/targetdomain/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
}
# Deny access to hidden files (.env, .git)
location ~ /\. {
deny all;
}
}
Phase 3: Zero-Loss Database & Code Migration
1. Freezing the Old Application
To prevent split-brain transactions, place the old cPanel site into maintenance mode with a secret bypass token:
php artisan down --secret="pre-cutover-bypass-key"
2. Atomic Database Dump
Execute an atomic dump of the MySQL database. Using --single-transaction ensures table locks are avoided whilst capturing an exact snapshot of relational tables:
mysqldump --single-transaction --quick -u cpanel_dbuser -p cpanel_dbname > db_backup.sql
Compress and transfer the database directly to the VPS over SSH/SCP:
gzip db_backup.sql
scp db_backup.sql.gz root@vps_ip:/tmp/
On the VPS, create the target database and restore the data:
mysql -u root -p -e "CREATE DATABASE laravel_prod CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -u root -p -e "CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'StrongGeneratedPassword';"
mysql -u root -p -e "GRANT ALL PRIVILEGES ON laravel_prod.* TO 'laravel_user'@'localhost'; FLUSH PRIVILEGES;"
gunzip < /tmp/db_backup.sql.gz | mysql -u laravel_user -p laravel_prod
3. Transferring Codebase & Sanitising Permissions
Exclude temporary cache directories and upload assets cleanly:
rsync -avz --exclude='storage/logs/*' --exclude='bootstrap/cache/*' \
/home/cpaneluser/public_html/ root@vps_ip:/var/www/targetdomain/
Fix Linux file ownership so Nginx and PHP-FPM can write to storage without requiring insecure 777 permissions:
sudo chown -R www-data:www-data /var/www/targetdomain
sudo find /var/www/targetdomain -type f -exec chmod 644 {} \;
sudo find /var/www/targetdomain -type d -exec chmod 755 {} \;
sudo chmod -R 775 /var/www/targetdomain/storage /var/www/targetdomain/bootstrap/cache
Update your /var/www/targetdomain/.env file with the new database credentials and run cache optimisation:
php artisan config:clear
php artisan cache:clear
php artisan view:cache
php artisan config:cache
php artisan storage:link
Phase 4: Cron Scheduler & Email Cutover
1. Enabling the Laravel Scheduler
In cPanel, developers often configure individual cron jobs for every command. On a VPS, you only need Laravel’s single master runner:
sudo crontab -u www-data -e
Add the canonical entry:
* * * * * cd /var/www/targetdomain && php artisan schedule:run >> /dev/null 2>&1
2. Business Email Migration
If email accounts were hosted on the cPanel server, you have two choices:
- Option A (Recommended): Decouple email from web hosting entirely. Point MX records to Google Workspace, Microsoft 365, or a dedicated provider (Zoho/Proton).
- Option B (VPS Webmail): If running email on the VPS, install Postfix/Dovecot or use your VPS control panel (e.g. Hostinger hPanel Mail / CyberPanel). Ensure SPF and DKIM are re-generated and published in DNS immediately to prevent outward messages hitting recipient spam boxes.
Phase 5: The Cutover & Verification
1. Updating the DNS A Record
Change your domain’s A record to point to the new VPS IP address:
Type: A
Name: @
Value: [YOUR_NEW_VPS_IP]
TTL: 300
Because we lowered the TTL to 300 seconds 24 hours ago, global traffic will transition to the new server within 5 minutes.
2. Verification Protocol
Run a live cURL test against the new server to confirm headers, SSL handshake, and response code:
$ curl -Iv -H "Host: targetdomain.com" https://[YOUR_NEW_VPS_IP]/
Verify that:
- HTTP status returns
200 OK. x-powered-byheaders are stripped or secure.- Storage symlinks serve media files correctly.
- Background queue workers or cron jobs fire on schedule.
Finally, bring the new application live:
php artisan up
🔗 Interconnected Engineering Triages & Architecture Mesh:
Infrastructure bottlenecks are rarely isolated. Upstream server performance, database query patterns, and DOM execution are tightly interlocked:
- Fixing TTFB > 2.5s on High-Traffic LiteSpeed Servers: Eliminate phantom cookie cache bypass and optimize external worker pools.
- Debugging 504 Gateway Timeouts in Nginx & PHP-FPM: Resolve upstream socket exhaustion and pool queue stalls.
- Eliminating Render-Blocking CSS & Optimizing Critical Path: Inline above-the-fold styles and defer secondary stylesheets for sub-800ms FCP.
Need Direct Implementation on Your Server?
Executing surgical server reconfigurations, database indexing, or JavaScript deferrals on live production environments carries downtime risk if done blindly. If you prefer a hands-off, risk-free resolution, I can triage and implement this exact patch directly on your origin stack under our Result-First, Fair-Pay model.
Need Quick Technical Eyes on an Active Error?
Drop your terminal log, stack trace, or server URL anytime — 7 days a week. Pure asynchronous text triage directly with Senior Systems Auditor Damar. No unscheduled phone calls required.