Target Architecture

This tutorial deploys a Laravel application to an Ubuntu 22.04 or 24.04 LTS VPS using Nginx, PHP-FPM 8.3, and MySQL 8. The stack is battle-tested, works with every Laravel version from 10 onward, and gives you fine control over performance and security. You'll need a registered domain, DNS pointing at the VPS, and SSH access as a sudo user.

Install the LEMP Stack

Update packages and install Nginx, MySQL, and PHP along with the extensions Laravel requires:

sudo apt update && sudo apt upgrade -y
sudo apt install nginx mysql-server php-fpm php-cli php-mysql php-mbstring \
    php-xml php-bcmath php-curl php-zip php-gd php-intl php-redis \
    unzip git curl -y

Confirm services are running:

sudo systemctl status nginx
sudo systemctl status mysql
sudo systemctl status php8.3-fpm

If you're on Ubuntu 22.04 the default package is php8.1-fpm. Add the ppa:ondrej/php repository if you need PHP 8.3 on 22.04.

Install Composer

Composer manages Laravel's dependencies. Install it globally so the composer binary is available to the deploy user:

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

Create the Database

Log into MySQL and create a dedicated database and user for the app. Never use the root MySQL account for application access.

sudo mysql
CREATE DATABASE laravel CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'laravel'@'localhost' IDENTIFIED BY 'StrongPasswordHere';
GRANT ALL PRIVILEGES ON laravel.* TO 'laravel'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Run sudo mysql_secure_installation on fresh installs to remove anonymous users and test databases.

Deploy the Application Code

Clone the Laravel project into /var/www. Use a deploy user so Nginx and PHP-FPM can read files without running as root.

sudo mkdir -p /var/www/laravel
sudo chown $USER:www-data /var/www/laravel
cd /var/www/laravel
git clone https://github.com/yourorg/yourapp.git .
composer install --no-dev --optimize-autoloader

Copy the environment template and generate the application key:

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

Edit .env with your database credentials, APP_ENV=production, APP_DEBUG=false, and the production APP_URL. Then run migrations and cache config:

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

Set File Permissions Correctly

Laravel needs write access to storage/ and bootstrap/cache/ but nothing else should be writable by the web server.

sudo chown -R www-data:www-data /var/www/laravel
sudo find /var/www/laravel -type f -exec chmod 644 {} \;
sudo find /var/www/laravel -type d -exec chmod 755 {} \;
sudo chmod -R ug+rwx /var/www/laravel/storage /var/www/laravel/bootstrap/cache

This pattern keeps the code read-only for the web user while allowing Laravel to write cache files and session data.

Configure Nginx for Laravel

Create /etc/nginx/sites-available/laravel with a Laravel-aware server block. The try_files directive routes every request through index.php, which is how Laravel's router picks up requests.

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/laravel/public;
    index index.php;

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

    charset utf-8;
    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

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

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
    }

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

Enable the site and reload:

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

Check out our Nginx reverse proxy guide for more advanced tuning such as rate limiting and gzip.

Enable HTTPS with Let's Encrypt

Once DNS propagates, issue a free certificate with Certbot:

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

Certbot configures auto-renewal via a systemd timer. For EV or wildcard needs, see our SSL certificate options or our SSL install walkthrough.

Queue Workers and the Scheduler

Most production Laravel apps need a background worker and the scheduler. Install Supervisor and create a worker config:

sudo apt install supervisor -y
sudo nano /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
command=php /var/www/laravel/artisan queue:work --sleep=3 --tries=3
autostart=true
autorestart=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/laravel-worker.log

Reload Supervisor with sudo supervisorctl reread && sudo supervisorctl update. For the scheduler, add one cron entry as the deploy user:

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

Deployment Checklist

Running a production Laravel stack? MassiveGRID's Cloud VPS gives you NVMe storage, root access, and 85+ global datacenter locations for reliable Laravel hosting. Contact our team to scope your deployment.

Published by MassiveGRID, your trusted Linux VPS hosting partner. Explore our Cloud VPS plans for root-access Ubuntu hosting.

Need a fast, reliable Linux VPS?

Dedicated vCPU, NVMe storage and full root access, deployed in minutes and resizable without a rebuild.

Configure a VPS

Further Reading