"How do I deploy my Laravel app to a server" has three real answers — and as people who have done all three, we'll show the difference with commands, not marketing. Your choice comes down to two things: how much control you want, and whose phone rings at 3am.
Path 1: cPanel / Shared Hosting — The "I Uploaded Files, Site Opened" Lie
The classic path: FTP the project that ran under php artisan serve into public_html. The site technically opens — then reality starts:
composer installcan't run (no SSH) → you FTP vendor/, it breaks on first updatephp artisan migrate→ no SSH, no panel equivalent; phpMyAdmin SQL imports are miseryqueue:work→ no persistent processes; the queue never runsschedule:run→ "cron" is a URL ping once a minute — fragileAPP_ENV=production→ lives in .htaccess, gets committed by accident
Outcome: a site that looks alive but is half a Laravel — no queue, no real cron. Our Laravel shared-hosting guide maps this wall fully — if you must stay on that path, start there. But the other two paths here are better.
Path 2: VPS + Manual Setup — The Real Commands
A clean Ubuntu VPS needs all of these done correctly for production Laravel:
1) Stack install
apt update && apt install -y nginx php8.3-fpm php8.3-cli php8.3-mysql \\
php8.3-mbstring php8.3-xml php8.3-curl php8.3-zip composer nodejs npm
2) Code and dependencies
cd /var/www/app && git pull origin main
composer install --no-dev --optimize-autoloader
npm ci && npm run build
php artisan migrate --force
php artisan config:cache && php artisan route:cache && php artisan view:cache
3) Nginx vhost (the critical line: public/ root)
server {
listen 80;
server_name site.com;
root /var/www/app/public; # ROOT is public/, NOT the project root!
index index.php;
location / { try_files $uri $uri/ /index.php?$query_string; }
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}
4) Queue worker — persistent via Supervisor
# /etc/supervisor/conf.d/app-worker.conf
[program:app-worker]
command=php /var/www/app/artisan queue:work --sleep=3 --tries=3
autostart=true
autorestart=true
numprocs=2
user=www-data
5) Scheduler — one cron line
* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1
6) SSL + monitoring + backups
Certbot for SSL; log rotation; an uptime monitor; pg_dump cron — each one is separately your problem. First setup: half a day. Monthly maintenance: a few hours. When supervisor dies at 3am: you.
Path 3: PaaS — Git Push, the Platform Handles the Rest
git push origin main
That's it. The platform detects the framework, runs the build, executes composer install --optimize, migrates, boots the container, attaches SSL. Queue workers and the scheduler are defined as processes inside the same project — no supervisor conf. Zero-downtime: the old process stays up until the new one is healthy.
The Honest Three-Way Table
| cPanel/FTP | VPS + Manual | PaaS | |
|---|---|---|---|
| Setup time | 1 hour (+years of hacks) | Half a day to a day | 10 minutes |
| Queue/cron | None | Supervisor + cron — you build it | Built-in |
| Deploy speed | Minutes via FTP, half-file risk | SSH script — if you wrote one | git push → 2 min |
| Rollback | Re-upload old files | git revert + manual | One click |
| SSL | Panel click | Certbot — you | Automatic |
| Ops load | Low but capped | High — full sysadmin | Zero |
| Control | Low | Full (root) | Platform boundary |
The VPS's single win is control — kernel, custom services, custom networking. You can build everything yourself; the question is do you want to? Our VPS vs PaaS analysis opens the cost side of that decision.
The Decision Formula
- A real Laravel app, not WordPress/static → rule out cPanel.
- Is Linux ops your job or your hobby? Yes → VPS. No → PaaS.
- When supervisor dies at 3am, who do you want waking up? That's your answer.
On Skyversal, web + worker + cron + DB run together inside one project — no triple-invoice dyno model. The environment variables guide and hosting guide cover migration details; pricing is in TRY.
FAQ
Laravel "works" on cPanel — why move?
An opening site ≠ a working application. Without queue, scheduler and composer, half of Laravel is disabled — and every deploy risks half-written files.
What gets forgotten most on a VPS?
Nginx root pointing at public/ (not the project root!), storage/ and bootstrap/cache permissions, supervisor's www-data user, and the schedule:run cron line — the four classic sources of "site works but X doesn't".
How are queues and cron defined on a PaaS?
As processes inside the project: queue:work is a worker process, schedule:run a scheduled task in the panel. No supervisor conf, no crontab editing.
Anything different for Laravel 12?
Same flow — just PHP 8.2+ and current composer. Version notes live in our Laravel 12 upgrade guide.