How to Deploy Laravel on an Ubuntu VPS: From Zero to Live

Deploying Laravel to a VPS (Virtual Private Server) is a major milestone when your project is ready for real users: you get more stable performance, full control over the server configuration, and a clearer path to scaling compared to shared hosting. For beginners, though, the process can feel overwhelming—installing a web server, PHP, a database, setting file permissions, and enabling SSL.

How to Deploy Laravel on an Ubuntu VPS: From Zero to Live

In this tutorial, you’ll deploy Laravel on an Ubuntu VPS from absolute zero until your application is reachable via domain/HTTPS. The focus is a production-ready setup (not just “it loads in the browser”), using safer practices: a non-root user, firewall rules, correct Nginx configuration, clean permissions, and Laravel production optimization.

End goal: your Laravel app is online using Nginx + PHP-FPM, connected to a database, and served over HTTPS.


Prerequisites

Before you start, prepare:

  • Ubuntu VPS 22.04 LTS / 24.04 LTS (recommended for 2026)
  • SSH access to the server (IP + username + password or SSH key)
  • A domain (optional, but strongly recommended for SSL). Example: example.com
  • A Laravel project ready to deploy (in a Git repo or on your local machine)
  • Basic Linux command knowledge (cd, ls, nano, systemctl)

Minimum specs (typical):

  • 1 vCPU
  • 1–2 GB RAM (2 GB is more comfortable if you use queue/redis)
  • 20 GB storage

Deployment architecture used

In this tutorial we’ll use a common and maintainable stack:

  • Nginx as the web server and reverse proxy
  • PHP-FPM to execute PHP (Laravel)
  • MySQL/MariaDB as the database (you can replace it with PostgreSQL; the concept is similar)
  • Certbot (Let’s Encrypt) for HTTPS SSL
  • (Optional) Redis for cache/queue
  • (Optional) Supervisor for queue workers

Simple request flow:

  1. A user visits https://example.com
  2. Nginx receives the request on port 443
  3. Nginx serves from Laravel’s public/ directory
  4. For PHP requests, Nginx forwards to PHP-FPM
  5. Laravel reads .env and connects to the database

Important Laravel folders:

  • public/ → the document root for your web server (required)
  • storage/ and bootstrap/cache/ → must be writable by the web server user
  • .env → production environment configuration

Step 1 — Update the server + basic security

Log in to your VPS:

ssh root@YOUR_SERVER_IP

1A) Update packages

apt update
apt -y upgrade
apt -y autoremove

If the kernel/core components were updated, reboot:

reboot

1B) Create a non-root user + sudo

It’s best not to work daily as root.

adduser deploy
usermod -aG sudo deploy

Try logging in as the new user:

ssh deploy@YOUR_SERVER_IP

1C) Enable the UFW firewall

Install UFW and set minimal rules:

sudo apt -y install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

Step 2 — Install core components (Nginx, PHP, Composer, DB)

2A) Install Nginx

sudo apt -y install nginx
sudo systemctl enable --now nginx

Check status:

sudo systemctl status nginx

2B) Install PHP-FPM + extensions required by Laravel

In 2026, common Laravel PHP versions are 8.2 or 8.3. On Ubuntu, the exact version may vary. Install these core packages:

sudo apt -y install \
  php-fpm php-cli php-common \
  php-mbstring php-xml php-curl php-zip php-bcmath php-intl \
  php-mysql

Check PHP version:

php -v

List PHP-FPM services (name may vary, e.g. php8.3-fpm):

systemctl list-units --type=service | grep php

2C) Install Composer

A safe option on Ubuntu is the package manager (it may not be the newest release, but it’s stable). For this tutorial:

sudo apt -y install composer
composer --version

2D) Install MariaDB (MySQL-compatible)

sudo apt -y install mariadb-server
sudo systemctl enable --now mariadb

Run basic hardening:

sudo mysql_secure_installation

Create a database + dedicated app user:

sudo mariadb

In the MariaDB prompt:

CREATE DATABASE laravel_app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'STRONG_PASSWORD_HERE';
GRANT ALL PRIVILEGES ON laravel_app.* TO 'laravel_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Step 3 — Prepare the app directory and deploy your code

We’ll place the project under /var/www.

3A) Create the project directory

sudo mkdir -p /var/www/laravel-app
sudo chown -R deploy:deploy /var/www/laravel-app

3B) Deploy via Git (recommended)

Install git:

sudo apt -y install git

Clone the repo (example):

cd /var/www
git clone <https://github.com/username/your-laravel-repo.git> laravel-app
cd /var/www/laravel-app

If the repo is private, use a deploy key or a secure token approach (don’t hardcode secrets on the server).

Alternative: upload files (scp/rsync)

If you don’t use Git, you can upload from your laptop:

rsync -avz --exclude node_modules --exclude vendor ./ deploy@YOUR_SERVER_IP:/var/www/laravel-app/

Step 4 — Configure Laravel for production

Enter the project directory:

cd /var/www/laravel-app

4A) Install production dependencies

composer install --no-dev --optimize-autoloader

4B) Set up environment variables (.env)

Create .env from the example:

cp .env.example .env

Edit .env:

nano .env

At minimum, set:

  • APP_NAME="Laravel App"
  • APP_ENV=production
  • APP_DEBUG=false
  • APP_URL=https://example.com
  • DB_CONNECTION=mysql
  • DB_DATABASE=laravel_app
  • DB_USERNAME=laravel_user
  • DB_PASSWORD=STRONG_PASSWORD_HERE

Generate the app key:

php artisan key:generate

4C) Run database migrations

In production, use --force so it doesn’t prompt/block.

php artisan migrate --force

4D) Correct permissions (most common source of errors)

Nginx/PHP-FPM typically runs as www-data. Laravel needs write access to:

  • storage/
  • bootstrap/cache/

Set ownership and permissions safely:

sudo chown -R www-data:www-data /var/www/laravel-app/storage /var/www/laravel-app/bootstrap/cache
sudo find /var/www/laravel-app/storage -type d -exec chmod 775 {} \;
sudo find /var/www/laravel-app/bootstrap/cache -type d -exec chmod 775 {} \;

Note: avoid chmod -R 777 because it’s a security risk.


Step 5 — Configure Nginx for Laravel

5A) Create an Nginx server block

Create a config file:

sudo nano /etc/nginx/sites-available/laravel-app

Example configuration (adjust the domain and PHP-FPM socket version):

server {
    listen 80;
    server_name example.com www.example.com;

    root /var/www/laravel-app/public;
    index index.php index.html;

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

    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 ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

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

Enable the site and (optionally) disable the default site:

sudo ln -s /etc/nginx/sites-available/laravel-app /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default

Test the config:

sudo nginx -t

Reload Nginx:

sudo systemctl reload nginx

At this point, your app should be reachable via HTTP if your DNS points to the VPS.


Step 6 — Enable HTTPS SSL (Let’s Encrypt)

6A) Install Certbot

sudo apt -y install certbot python3-certbot-nginx

6B) Request a certificate

Make sure your domain already points to the VPS IP, then run:

sudo certbot --nginx -d example.com -d www.example.com

Choose the HTTP → HTTPS redirect option if prompted.

6C) Verify auto-renewal

sudo systemctl status certbot.timer
sudo certbot renew --dry-run

Step 7 — Optimize Laravel for production

Once online, enable caches for better performance:

php artisan config:cache
php artisan route:cache
php artisan view:cache

Important notes:

  • route:cache is safe if your routes don’t use closures that can’t be serialized.
  • After changing config/routes, rebuild the caches.

For safer permissions after deploy:

  • don’t make the entire project owned by www-data
  • only make writable directories (storage/, bootstrap/cache/) owned/writable as needed

Step 8 — Scheduler and queue (optional but recommended)

8A) Laravel Scheduler (cron)

Run as an appropriate user (often www-data or your deploy user). Add a cron entry:

sudo crontab -u www-data -e

Add:

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

8B) Queue worker (Supervisor)

If your app uses queues, use Supervisor:

sudo apt -y install supervisor

Create the config:

sudo nano /etc/supervisor/conf.d/laravel-worker.conf

Example:

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

Reload Supervisor:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status

Common troubleshooting (frequent errors)

1) 403 Forbidden / weird 404 behavior

Common causes:

  • Nginx root is not pointing to public/
  • incorrect directory permissions

Verify your Nginx config contains:

  • root /var/www/laravel-app/public;

2) 502 Bad Gateway

Usually PHP-FPM is down or the socket is wrong.

Check PHP-FPM:

sudo systemctl status php8.3-fpm

Check available sockets:

ls -lah /run/php/

Make sure fastcgi_pass matches your PHP-FPM socket.

3) Database connection errors

Confirm .env values and that the DB user has access.

Test DB login:

sudo mariadb -u laravel_user -p laravel_app

4) Storage/log not writable

Ensure writable directories are owned correctly:

sudo chown -R www-data:www-data /var/www/laravel-app/storage /var/www/laravel-app/bootstrap/cache

5) Check logs

  • Nginx error log:
sudo tail -n 100 /var/log/nginx/error.log
  • Laravel log:
tail -n 100 /var/www/laravel-app/storage/logs/laravel.log
  • systemd logs:
sudo journalctl -u nginx -n 100 --no-pager

Final checklist: Laravel is online

  • [ ] Your domain points to YOUR_SERVER_IP
  • [ ] Nginx is active and config is valid (nginx -t is OK)
  • [ ] Nginx root points to public/
  • [ ] .env is production-ready (APP_DEBUG=false)
  • [ ] Migrations have run (migrate --force)
  • [ ] storage/ and bootstrap/cache/ permissions are correct
  • [ ] HTTPS is enabled and certbot auto-renewal works
  • [ ] Laravel caches are built (config/route/view caches)

FAQ (for SEO)

1) What’s the difference between deploying Laravel on a VPS vs shared hosting?

A VPS gives you full control (Nginx, PHP-FPM, Supervisor, Redis) and is typically more stable and scalable.

2) Why must the document root point to public?

Only public/ should be exposed to the internet. Other folders contain sensitive files.

3) Do I need a domain to deploy Laravel?

Not strictly for testing via IP, but Let’s Encrypt SSL generally requires a domain.

4) Why do we run php artisan migrate with --force in production?

Laravel blocks migrations in production unless you use --force, to prevent accidental destructive actions.

5) Nginx looks correct but I still get 502. What’s the most common cause?

PHP-FPM is not running, or the fastcgi_pass socket doesn’t match your installed PHP-FPM version.

6) How do I update Laravel code after it’s online?

Typically: git pull, composer install --no-dev, then rebuild caches and run migrations if needed.

7) Do I need Redis for Laravel?

Not required, but very helpful for caching and queues on larger apps.

8) What’s the minimum RAM for Laravel on a VPS?

A lightweight app can run on 1 GB, but 2 GB is safer, especially with a database and queue workers.

Komentar