Deploying a PHP Application to cPanel Without Downtime
Shared hosting is still where a lot of business software lives. Here is the deployment routine that keeps sites online during releases.
Plenty of small and medium businesses run on shared hosting with cPanel, and that is not going to change overnight. The challenge is deploying updates without taking the site offline or, worse, leaving a half-updated application behind.
The problem with uploading over live files
Copying files directly into public_html means visitors can load a page that mixes new templates with old code. If your release includes a database migration, the window where old code meets a new schema is exactly when things break.
The release-directory approach
Keep releases outside the document root and swap a symlink:
/home/user/
├── apps/
│ ├── releases/
│ │ ├── 2026-09-14-1015/
│ │ └── 2026-10-02-0940/
│ └── current -> releases/2026-10-02-0940
└── public_html -> apps/current/public
Upload the new version to a fresh release directory, then flip the symlink. The switch is atomic, so no request ever sees a mixed state.
ln -sfn /home/user/apps/releases/2026-10-02-0940 /home/user/apps/current-new
mv -T /home/user/apps/current-new /home/user/apps/current
Make the application environment-aware
Never commit .env files or credentials into a release. Keep one configuration file outside the web root and symlink it in during release:
ln -sfn /home/user/apps/shared/.env /home/user/apps/current/.env
Run migrations before flipping the link
Order matters: migrate the database first, then switch traffic. For zero-downtime schema changes, add columns as nullable with defaults, deploy the code that uses them, backfill in batches, and only then enforce constraints.
ALTER TABLE invoices ADD COLUMN reference VARCHAR(40) NULL;
Put the site into maintenance mode for the migration window
Most releases do not need it, but migrations do. Serve a static maintenance page from the web server level, allow your own IP, and keep it short.
# .htaccess
RewriteEngine On
RewriteCond %{REMOTE_ADDR} !^203\.0\.113\.10$
RewriteCond %{REQUEST_URI} !maintenance\.html$
RewriteRule .* /maintenance.html [R=503,L]
Keep the last three releases
If a release misbehaves, flipping the symlink back is a ten-second rollback — provided the previous release directory is still there.
Automate it
All of this can run from a Git post-receive hook or a small deploy script over SSH. The routine is: fetch, install dependencies, run migrations, warm caches, flip the symlink, restart queues, health-check, and roll back automatically if the health check fails.
Deployment should be the least exciting part of your week. When it is boring, it is working.