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 four global data centers 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.