Deploy Laravel on Vultr VPS 2026: Full Stack PHP Setup Guide

This post contains affiliate links. If you purchase through our links, we may earn a commission at no extra cost to you.

Deploy Laravel on Vultr VPS 2026 — Full Stack PHP Setup Guide
Deploy Laravel on Vultr VPS 2026 — Full Stack PHP Setup Guide

Shared hosting will quietly break your Laravel app. Queue workers won’t run, you can’t pin a PHP version, and the moment your .env file gets exposed in a public directory, you’ve got a real problem. A VPS fixes all of that, and Vultr’s High Performance tier gives you NVMe SSD storage at $12/month — the same price as the “optimized” shared plans that don’t even support PHP-FPM. This guide takes you from a blank Ubuntu 24.04 server to a production-ready Laravel deployment with SSL, Supervisor-managed queue workers, and cron scheduling. I’ve laid out every real command in the order you actually run them.


What You’ll Need (Prerequisites)

  • A domain name pointed at your server’s IP (A record set before Step 8)
  • Your Laravel app in a Git repo (GitHub, GitLab, or self-hosted)
  • Basic SSH comfort — you don’t need to be a sysadmin, but you should know how to connect and run commands
  • About 30–45 minutes

No Docker. No CI/CD pipeline required. This is the manual, direct setup that works.


Step 1 — Create Your Vultr VPS

🎁 Get $300 free credit when you sign up (valid for 30 days, limited-time offer) — Claim your credit →

Pick the right plan. The $5/month 1 GB RAM option looks tempting, but it’ll OOM (out-of-memory kill) when PHP-FPM, MySQL, Nginx, and a queue worker are all running at the same time. Don’t find that out in production.

Recommended plan: Cloud Compute → High Performance (AMD), 1 vCPU, 2 GB RAM, 50 GB NVMe SSD — $12/month. New accounts get $300 in free cloud credits (valid for 30 days).

Check out our Vultr pricing breakdown if you want to compare the full plan matrix before committing.

Setup steps:

  1. Log in to Vultr → click Deploy
  2. Select Cloud Compute → High Performance (AMD)
  3. Pick the datacenter closest to your users
  4. OS: Ubuntu 24.04 LTS
  5. Plan: 2 GB RAM / $12/month
  6. Enable backups ($2.40/mo — worth it)
  7. Add your SSH public key
  8. Click Deploy Now

Wait about 60 seconds for provisioning, then grab the IP from the dashboard.


Step 2 — Connect and Update the Server

ssh root@YOUR_SERVER_IP

Update packages and create a non-root deploy user:

apt update && apt upgrade -y
adduser deploy
usermod -aG sudo deploy

Copy your SSH key to the new user, then disable root login by editing /etc/ssh/sshd_config and setting PermitRootLogin no. From here, use the deploy user for everything.

Set up UFW firewall before you install anything else:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

💡 Tip: Prefer a visual approach? You can build these rules with our free Firewall Rule Builder — it generates ufw and iptables commands right in your browser.


Step 3 — Install PHP 8.3, Nginx, and Composer

PHP 8.3 is the 2026 production standard for Laravel — it’s what ships in Ubuntu 24.04’s default repos, and it satisfies both Laravel 12 and Laravel 13 requirements.

Install Nginx:

sudo apt install nginx -y
sudo systemctl enable nginx

Install PHP 8.3 and all the extensions Laravel needs:

sudo apt install php8.3 php8.3-fpm php8.3-mysql php8.3-mbstring \
  php8.3-xml php8.3-curl php8.3-zip php8.3-bcmath php8.3-tokenizer \
  php8.3-json php8.3-redis -y
sudo systemctl enable php8.3-fpm

Install Redis (for cache, sessions, and queue connections):

sudo apt install redis-server -y
sudo systemctl enable redis

Install Composer:

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php composer-setup.php
sudo mv composer.phar /usr/local/bin/composer

Verify everything installed cleanly:

php -v
nginx -v
composer --version

Step 4 — Install and Configure MySQL

sudo apt install mysql-server -y
sudo mysql_secure_installation

Run through the secure installation prompts — set a root password, remove anonymous users, disable remote root login. Then create your Laravel database and user:

sudo mysql -u root -p
CREATE DATABASE laravel_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'laravel'@'localhost' IDENTIFIED BY 'YourStrongPassword!';
GRANT ALL PRIVILEGES ON laravel_db.* TO 'laravel'@'localhost';
FLUSH PRIVILEGES;
EXIT;

utf8mb4 is the right charset — it handles emoji and full Unicode, unlike the legacy utf8 alias.


Step 5 — Clone Your Laravel App and Configure .env

cd /var/www
sudo git clone https://github.com/youruser/yourapp.git laravel
sudo chown -R www-data:www-data /var/www/laravel

Install Composer dependencies (no dev packages in production):

cd /var/www/laravel
sudo -u www-data composer install --no-dev --optimize-autoloader

Copy the environment file and generate an app key:

cp .env.example .env
php artisan key:generate

Open .env and fill in your production values:

APP_ENV=production
APP_DEBUG=false
APP_URL=https://yourdomain.com

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_db
DB_USERNAME=laravel
DB_PASSWORD=YourStrongPassword!

CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis

APP_DEBUG=false is non-negotiable in production. Leaving it true exposes stack traces — and your env values — in browser error messages.


Step 6 — Set Permissions and Run Artisan Commands

Laravel needs write access to storage and bootstrap/cache:

sudo chmod -R 755 /var/www/laravel/storage
sudo chmod -R 755 /var/www/laravel/bootstrap/cache

Run migrations, create the storage symlink, and cache everything:

php artisan migrate --force
php artisan storage:link
php artisan optimize

php artisan optimize is the Laravel 12+ single-command replacement for the old config:cache && route:cache && view:cache sequence. It handles config, events, routes, and views in one shot. Run it on every deployment.


Step 7 — Configure Nginx for Laravel

Create the server block at /etc/nginx/sites-available/laravel.conf:

server {
    listen 80;
    listen [::]:80;
    server_name yourdomain.com www.yourdomain.com;
    root /var/www/laravel/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";

    index index.php;
    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_hide_header X-Powered-By;
    }

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

The fastcgi_pass unix:/var/run/php/php8.3-fpm.sock line connects Nginx to PHP-FPM via a Unix socket — lower overhead than a TCP connection. The deny all block on dotfiles keeps your .env from being served directly.

Enable the config and reload:

sudo ln -s /etc/nginx/sites-available/laravel.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

Visit your server’s IP in a browser — you should see your Laravel app.


Step 8 — Enable SSL with Certbot

Your domain’s DNS A record needs to be pointing at the VPS IP before this step.

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx --redirect -d yourdomain.com -d www.yourdomain.com \
  -m [email protected] --agree-tos

Certbot verifies domain ownership, issues a 90-day Let’s Encrypt certificate, rewrites your Nginx config to redirect HTTP → HTTPS, and sets up a systemd timer for auto-renewal. Verify the renewal process works:

sudo certbot renew --dry-run

Open the HTTPS port if you haven’t already:

sudo ufw allow 443/tcp
sudo ufw reload

Step 9 — Set Up Queue Workers with Supervisor

Supervisor keeps your queue workers running as background processes and restarts them automatically if they crash.

sudo apt install supervisor -y
sudo systemctl enable supervisor

Create the worker config at /etc/supervisor/conf.d/laravel-worker.conf:

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/laravel/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
user=www-data
numprocs=3
redirect_stderr=true
stdout_logfile=/var/www/laravel/storage/logs/worker.log
stopwaitsecs=3600

numprocs=3 runs three parallel workers — enough for most small-to-medium apps on the 2 GB plan. If you’re using Laravel Horizon instead, set numprocs=1 and change the command to php /var/www/laravel/artisan horizon. Horizon manages its own worker pool internally.

Load and start the workers:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*
sudo supervisorctl status

You should see all three worker processes in RUNNING state.


Step 10 — Schedule Tasks with Cron

Laravel’s scheduler needs exactly one cron entry regardless of how many scheduled tasks your app has. Open the crontab for www-data:

💡 Tip: Not sure about the cron syntax? Our free Cron Expression Builder lets you set the schedule visually and previews the next five runs.

sudo crontab -u www-data -e

Add this single line:

* * * * * php /var/www/laravel/artisan schedule:run >> /dev/null 2>&1

The scheduler fires every minute and Laravel decides internally which tasks are due. All your scheduling logic lives in app/Console/Kernel.php (or the Schedule facade in Laravel 11+) — this cron entry just kicks the door open once a minute.


Performance Tips Before You Go Live

5 Laravel VPS performance tips: OPcache, artisan optimize, queue workers, MySQL tuning, Nginx caching
5 Laravel VPS performance tips: OPcache, artisan optimize, queue workers, MySQL tuning, Nginx caching

1. Enable OPcache. Add these settings to /etc/php/8.3/fpm/conf.d/10-opcache.ini:

opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.save_comments=1

OPcache stores compiled PHP bytecode in memory, eliminating repeated file parsing on every request. On a 2 GB VPS, memory_consumption=256 is safe and leaves plenty of headroom.

2. Run php artisan optimize on every deploy. Not just once during setup — every time you push code. It caches config, events, routes, and views. If you’re changing .env values, run php artisan config:clear first, then optimize.

3. Use Redis for everything. The .env settings CACHE_DRIVER=redis, SESSION_DRIVER=redis, and QUEUE_CONNECTION=redis offload Laravel’s caching layer from the database. Your MySQL instance will thank you.

4. Tune Composer’s autoloader. The --optimize-autoloader flag on composer install generates a classmap instead of scanning directories on every request. Already covered in Step 5, but don’t skip it.

5. Consider Nginx FastCGI caching for high-traffic public pages. It caches PHP responses at the Nginx layer, bypassing PHP-FPM entirely for repeat requests. This is advanced territory — hold off until you’re actually seeing load.

For a deeper look at Vultr’s infrastructure and how it performs under real workloads, see our Vultr review.


Next Steps

If you followed this guide, you’ve got a production-grade Laravel setup on Vultr: Nginx serving traffic, PHP 8.3-FPM handling requests, MySQL storing data, Redis powering cache and queues, Supervisor keeping workers alive, Certbot managing SSL, and cron driving the scheduler. That’s the full stack, running on $12/month.

The next improvements worth thinking about:

  • Zero-downtime deploys: Implement the atomic symlink approach or add Deployer to your workflow
  • Laravel Horizon: Replace bare Supervisor workers with Horizon if you need a queue dashboard and dynamic scaling
  • Vultr Backups: Already enabled from Step 1 — test a restore before you need one
  • Monitoring: Add basic server monitoring (Vultr has built-in metrics; UptimeRobot handles uptime alerts for free)
Get started on Vultr →

New accounts get $300 in free cloud credits to get your server running — enough to run a $12/month VPS for over two years if you’re methodical about it.


Affiliate disclosure: This post contains affiliate links. If you purchase through our links, we may earn a commission at no extra cost to you.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top