How to Deploy a Laravel App to a VPS (Production-Ready)

Last updated August 21, 2026
# the short answer

To deploy a Laravel app to a VPS, provision an Ubuntu 22.04 or 24.04 server, install a LEMP stack (Nginx, PHP 8.3, MySQL or PostgreSQL) plus Composer and Node, then clone your repo with a Git deploy key. Set your .env, run composer install --no-dev, php artisan migrate --force, and cache config and routes. Point Nginx at the public directory, secure it with a Let's Encrypt certificate, and run queue workers under Supervisor with the scheduler on cron. For zero downtime, deploy into a timestamped release folder and atomically swap a symlink.

Skip the manual setup. DeployManage provisions your server and ships zero-downtime deploys on any cloud.

$ get started free

Deploying a Laravel app to a VPS means getting your code onto a Linux server, wiring up PHP, a web server, and a database, and then running the framework's build and cache steps so the app serves fast and stays up. This guide walks the full path from a bare Ubuntu box to an atomic, zero-downtime release you can roll back in seconds.

What you need before you start

Have three things ready: a VPS, a domain, and SSH access. Any provider works (Hetzner, DigitalOcean, Vultr, Linode, or AWS Lightsail); if you are on DigitalOcean specifically, the deploy Laravel to DigitalOcean walkthrough covers the droplet side of this. For a small to mid-size Laravel app, 2 vCPUs and 4 GB of RAM is a comfortable starting point. Ubuntu 22.04 LTS or 24.04 LTS are the safe choices because most PHP packages target them directly.

Point an A record for your domain at the server's public IP before you request a TLS certificate, since Let's Encrypt validates over HTTP and needs DNS to resolve first.

How do I deploy a Laravel project to a server?

You deploy a Laravel project to a server in six stages: provision and harden the OS, install the runtime stack, pull the code, configure the environment, run the build and migration commands, then front it with Nginx and SSL. Do them in that order and nothing surprises you later.

1. Provision and harden Ubuntu

SSH in as root, create a non-root deploy user, and lock down access before anything else.

adduser deploy
usermod -aG sudo deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy

# Basic firewall
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable

Disable password logins in /etc/ssh/sshd_config (set PasswordAuthentication no) and reload SSH. From here on, work as the deploy user.

2. Install the LEMP stack and PHP 8.3

Laravel 11 requires PHP 8.2 or newer, so PHP 8.3 is a solid target. On Ubuntu the cleanest route is Ondrej Sury's PPA, which keeps modern PHP builds current.

sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx mysql-server git unzip

sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
sudo apt install -y php8.3-fpm php8.3-cli php8.3-mysql \
  php8.3-mbstring php8.3-xml php8.3-curl php8.3-zip \
  php8.3-bcmath php8.3-gd php8.3-intl

Prefer PostgreSQL? Install postgresql and php8.3-pgsql instead of the MySQL packages. Then secure the database and create an app user:

sudo mysql_secure_installation

sudo mysql -e "CREATE DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
sudo mysql -e "CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'strong-password-here';"
sudo mysql -e "GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'localhost'; FLUSH PRIVILEGES;"

3. Install Composer and Node

Composer handles PHP dependencies. Node builds your front-end assets (Vite is the Laravel default).

curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

4. Clone the repo with a Git deploy key

Generate a key on the server and add the public half as a read-only deploy key in your Git host. This keeps deploys automated without exposing a personal account.

ssh-keygen -t ed25519 -C "deploy@yourdomain" -f ~/.ssh/deploy_key
cat ~/.ssh/deploy_key.pub   # paste into GitHub/GitLab deploy keys

cd /var/www
sudo mkdir yourapp && sudo chown deploy:deploy yourapp
git clone [email protected]:you/yourapp.git yourapp

5. Configure the environment and build

Copy .env.example to .env, set your database credentials, APP_URL, mail, and any queue or cache drivers. Set APP_ENV=production and APP_DEBUG=false so stack traces never leak to users. Then install dependencies for production and build assets.

composer install --no-dev --optimize-autoloader
npm ci && npm run build

php artisan key:generate
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache

The --force flag lets migrations run non-interactively in production. Caching config, routes, and views compiles them into single files, which noticeably cuts request overhead. Remember: after any config change you must rerun config:cache, because a cached config ignores .env at runtime.

6. Set storage permissions

Nginx and PHP-FPM run as the www-data user, so the framework's writable directories need the right ownership. Getting this wrong is the most common cause of a blank 500 on a fresh deploy.

sudo chown -R deploy:www-data /var/www/yourapp/storage /var/www/yourapp/bootstrap/cache
sudo chmod -R 775 /var/www/yourapp/storage /var/www/yourapp/bootstrap/cache
php artisan storage:link

How do I point Nginx at a Laravel app?

Point Nginx at the app's public directory, never the project root, and route all non-file requests through index.php. Create /etc/nginx/sites-available/yourapp:

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    root /var/www/yourapp/current/public;

    index index.php;
    charset utf-8;

    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;
    }

    location ~ /\.(?!well-known).* { deny all; }
}

Enable the site and reload:

sudo ln -s /etc/nginx/sites-available/yourapp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

How do I add a free SSL certificate?

Use Certbot with Let's Encrypt to issue and auto-renew a free certificate. It edits your Nginx config to serve HTTPS and sets up a renewal timer automatically.

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Certificates last 90 days and the installed systemd timer renews them well before expiry. Confirm the timer is active with systemctl status certbot.timer.

Queue workers and the scheduler

Two background pieces make a Laravel app production-ready: queue workers for jobs (email, notifications, exports) and the scheduler for recurring tasks. Run workers under Supervisor so they restart on failure and after deploys.

Create /etc/supervisor/conf.d/yourapp-worker.conf:

[program:yourapp-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/yourapp/current/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
user=deploy
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/yourapp/shared/worker.log
stopwaitsecs=3600
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start yourapp-worker:*

For the scheduler, add one cron entry that fires Laravel's scheduler every minute. Laravel decides internally what actually runs.

* * * * * cd /var/www/yourapp/current && php artisan schedule:run >> /dev/null 2>&1

How do I deploy Laravel with zero downtime?

You deploy Laravel with zero downtime by building each release in its own timestamped folder and switching a current symlink to it only after the build succeeds. Because the symlink swap is atomic, in-flight requests never see a half-updated app, and rolling back is just repointing the symlink at the previous release.

The layout looks like this:

/var/www/yourapp/
  releases/
    20260714093000/
    20260714101500/   <- newly built
  shared/
    .env
    storage/
  current -> releases/20260714101500

A deploy script clones or checks out the new commit into a fresh release directory, symlinks the shared .env and storage into it, installs dependencies, runs migrations, warms the caches, then swaps the symlink and reloads PHP-FPM:

REL=/var/www/yourapp/releases/$(date +%Y%m%d%H%M%S)
git clone --depth 1 [email protected]:you/yourapp.git $REL
ln -sfn /var/www/yourapp/shared/.env $REL/.env
ln -sfn /var/www/yourapp/shared/storage $REL/storage

cd $REL
composer install --no-dev --optimize-autoloader
npm ci && npm run build
php artisan migrate --force
php artisan config:cache && php artisan route:cache

ln -sfn $REL /var/www/yourapp/current
sudo systemctl reload php8.3-fpm
php /var/www/yourapp/current/artisan queue:restart

Call queue:restart after each deploy so workers pick up new code instead of running stale classes. Keep the last three to five releases so a rollback is instant. One caveat: a migration that drops a column mid-deploy can still break the old release for a moment, so treat schema changes as backward compatible (expand first, contract later).

Do I need a control panel to deploy Laravel?

No, you do not need a control panel: everything above works with plain SSH and shell scripts. The question is whether you want to own the maintenance. Manual setup gives you full control and costs nothing beyond the server, but you become responsible for security patches, PHP upgrades, certificate edge cases, and writing your own release and rollback tooling. A managed panel automates provisioning and turns deploys into a button press or a Git push.

TaskManual VPS setupManaged control panel
Initial provisioning1 to 3 hours by handMinutes, automated
Security patchingYour responsibilityAutomated updates
Zero-downtime deploysCustom scripts you maintainBuilt in
RollbackManual symlink swapOne click
SSL renewalCertbot timer you verifyManaged and monitored
Ongoing costServer onlyServer plus subscription

If you manage more than one app or a client fleet, a server management panel pays for itself quickly in saved hours and fewer 2 a.m. incidents. DeployManage provisions servers on any provider and runs zero-downtime deploys for you, so the release, symlink swap, and worker restart all happen without hand-rolled scripts. As you spread across more boxes and providers, it is worth tracking your cloud spend so a fleet of idle servers does not quietly inflate the bill.

What size VPS does a Laravel app need?

The number that decides this is not traffic, it is how much memory your PHP FPM pool and your queue workers hold at once. A PHP FPM worker running a mature Laravel app typically sits between 30 and 80 MB of resident memory, and each queue worker is a full PHP process with the entire framework booted, so it costs about the same as a web worker even while it is idle.

Server RAMRealistic Laravel workloadWhat breaks first
1 GBOne small app, low traffic, no queue workers The deploy. Composer install on a mature project can need more than 1 GB by itself
2 GBOne production app, a few FPM workers, one or two queue workers Concurrency during traffic spikes
4 GBOne busy app or several small ones, Redis, a handful of workers Database memory once the working set grows
8 GB and upMulti app servers, heavier queues, larger databases Nothing predictable. Measure rather than guess

The failure that catches most people out is on the 1 GB tier and it happens at deploy time, not under load. The site runs fine for weeks, then a composer install during a release exhausts memory and the deploy dies halfway. If you are running a 1 GB box, either add swap or build your dependencies somewhere else and ship the vendor directory with the release.

How much does a VPS for Laravel cost?

The server itself is the cheap part. A 2 GB DigitalOcean Basic droplet with 1 vCPU, 50 GB of disk and 2 TB of transfer is $12 per month, and the 4 GB tier is $24. What moves the total is everything you bolt on around it: managed database, object storage, backups, a control panel license and whatever you pay a person to maintain it. We worked the full picture through, including the point at which a managed host becomes the cheaper option, in the breakdown of what a Laravel VPS actually costs.

How do I install Laravel on Ubuntu?

Laravel itself installs with Composer in one command. The work is the stack underneath it, and it is worth being deliberate about which pieces you actually need rather than installing a metapackage and hoping.

ComponentWhat it doesSkip it when
PHP FPM with the app extensionsRuns the applicationNever
NginxServes static files and proxies PHP requestsNever on a web server
MySQL or PostgreSQLThe databaseYou use a managed database elsewhere
RedisCache, sessions and the queue driver Small apps that can live with the database queue driver
SupervisorKeeps queue workers alive and restarts them on deploy The app has no queued jobs, which is rarer than people think
CertbotIssues and renews the TLS certificate A proxy in front of the server terminates TLS for you
A firewallCloses everything except 22, 80 and 443Never

Set the PHP version deliberately rather than taking whatever the distribution ships. Ubuntu's default PHP is often a release or two behind what your Laravel version wants, and discovering that during a deploy is a bad time to find out. The full sequence, including the parts people forget like file permissions, log rotation and unattended security updates, is in the production server setup checklist.

Can I deploy Laravel without managing a server?

Yes, and it is a fair question to ask before you spend an afternoon on any of the above. There are three honest options and they trade cost against control in different directions.

OptionWhat you give upWhat you get
A fully managed platformRoot access, and you pay per app or per resource You never touch a server. Good when nobody on the team wants to
A managed host on your behalfThe server lives in the vendor's account Patching and support are somebody else's job
Your own VPS plus a panelAn afternoon of setup, or a few minutes with a panel Root, a cheaper box, and no per app meter

The third option is the one people underrate, because the assumption is that keeping root means doing everything by hand. It does not. A panel provisions the box, configures Nginx and PHP FPM, issues the certificate, keeps the queue workers supervised and runs the release with an atomic symlink switch, and the server still sits in a cloud account you control. That is the middle path between managing everything and handing the whole thing over, and it is covered in detail on the VPS management page and in managed against unmanaged VPS hosting.

Common gotchas

  • 500 after deploy, no error shown: almost always storage permissions or a stale cached config. Rerun config:cache and recheck ownership.
  • Vite assets 404: you forgot npm run build, or APP_URL is wrong.
  • Queued jobs run old code: you skipped queue:restart.
  • Certbot fails: DNS has not propagated, or port 80 is blocked by the firewall.

Get these six stages right once, script them, and every future deploy becomes boring in the best way. The next step, once the manual walkthrough works, is to stop running it by hand: deploying from Git builds each commit in a fresh release directory and promotes it atomically, keeping the previous release on disk for rollback.

Ready to stop managing servers by hand? DeployManage provisions, deploys and monitors your fleet from one dashboard.

$ get started free

Frequently asked questions

How do I deploy a Laravel project to a server?

Provision an Ubuntu VPS, install Nginx, PHP 8.3, and a database, then clone your repo with a Git deploy key. Configure the .env, run composer install --no-dev, php artisan migrate --force, and cache config and routes. Point Nginx at the public folder and add SSL with Certbot.

Do I need a control panel to deploy Laravel?

No. You can deploy Laravel with plain SSH and shell scripts. A control panel is optional but it automates provisioning, SSL, security patching, and zero-downtime deploys, which saves significant time once you manage more than one app or run production for clients.

How do I deploy Laravel with zero downtime?

Build each release in a timestamped folder, then atomically swap a current symlink to it after the build and migrations succeed. Since the swap is instant, requests never hit a half-updated app, and rolling back means repointing the symlink at the previous release.

Which PHP version does Laravel need on a VPS?

Laravel 11 requires PHP 8.2 or higher, so PHP 8.3 is a good production target. Install it via the ondrej/php PPA on Ubuntu along with common extensions like mbstring, xml, curl, mysql or pgsql, bcmath, and intl for full framework support.

How do I run Laravel queue workers on a VPS?

Run php artisan queue:work under Supervisor so workers start on boot and restart after crashes or deploys. Configure numprocs for concurrency, set autorestart to true, and call php artisan queue:restart after every deploy so workers load your newest code.

How do I get free SSL for a Laravel app on a VPS?

Install Certbot with the Nginx plugin and run certbot --nginx -d yourdomain.com. It issues a free Let's Encrypt certificate, edits your Nginx config for HTTPS, and installs a systemd timer that auto-renews the 90-day certificate before it expires.

What size VPS do I need for a Laravel app?

Two gigabytes of RAM is the realistic floor for a production Laravel app, and four is comfortable once you add Redis and several queue workers. A PHP FPM worker holds roughly 30 to 80 MB and each queue worker is a full PHP process. One gigabyte usually fails at deploy time rather than under traffic, when Composer runs out of memory.

How much does a VPS for Laravel cost per month?

A 2 GB DigitalOcean Basic droplet is $12 per month and the 4 GB tier is $24, both including 50 GB or more of disk and generous transfer. The server is rarely the expensive part. Managed databases, object storage, backups and a control panel license are what move the total, so budget the whole stack rather than the droplet.

Can I deploy Laravel without managing a server?

Yes. A fully managed platform removes the server entirely but bills per app and takes root away. A managed host runs the box in its own account. A panel on your own VPS is the middle path: it provisions, configures and deploys for you while the server stays in your cloud account and you keep root access.

Which packages does Laravel need on Ubuntu?

PHP FPM with the app extensions, Nginx, a database, Redis for cache and queues, Supervisor to keep queue workers alive, Certbot for TLS and a firewall. Set the PHP version deliberately rather than accepting the distribution default, which is often a release or two behind what your Laravel version expects.

# related

Provision and deploy from one dashboard.

Connect a provider and ship your first zero-downtime deploy in minutes.

$ get started free

# free plan · no credit card required