6 min read

Laravel is the most popular PHP framework for building web applications. This guide covers deploying a Laravel project on a GoZen VPS running Ubuntu 22.04 or 24.04 with Nginx, PHP-FPM, and MySQL.

Prerequisites

Step 1: Install PHP and Required Extensions

Current Laravel releases need PHP 8.3 or newer and several extensions. This guide installs PHP 8.4:

  sudo apt update && sudo apt upgrade -y

# Add the PHP repository
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update

# Install PHP and extensions
sudo apt install php8.4 php8.4-fpm php8.4-cli php8.4-mysql php8.4-pgsql \
  php8.4-mbstring php8.4-xml php8.4-curl php8.4-zip php8.4-bcmath \
  php8.4-gd php8.4-intl php8.4-redis php8.4-opcache -y
  

Verify the install:

  php -v
# Should show PHP 8.4.x
  

Step 2: Install Composer

Composer manages PHP dependencies:

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

Step 3: Install and Configure MySQL

  sudo apt install mysql-server -y
sudo mysql_secure_installation
  

Create a database and user for your app:

  sudo mysql
  
  CREATE DATABASE laravel_app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'laravel'@'localhost' IDENTIFIED BY 'your-strong-password-here';
GRANT ALL PRIVILEGES ON laravel_app.* TO 'laravel'@'localhost';
FLUSH PRIVILEGES;
EXIT;
  

If you use PostgreSQL instead, install postgresql postgresql-contrib and adjust the commands accordingly.

Step 4: Install Nginx and Redis

The app below uses Redis for cache, sessions, and queues, so install it now, before the app first runs:

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

Step 5: Deploy Your Laravel Project

Clone the Repository

  sudo mkdir -p /var/www/laravel-app
sudo chown $USER:$USER /var/www/laravel-app

cd /var/www
git clone [email protected]:yourusername/your-laravel-app.git laravel-app
cd laravel-app
  

Install Dependencies

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

The --no-dev flag skips development packages (PHPUnit, debugbar, etc.) which you don’t need in production.

Configure the Environment

  cp .env.example .env
nano .env
  

Update these values:

  APP_NAME="Your App Name"
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=https://yourdomain.com

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_app
DB_USERNAME=laravel
DB_PASSWORD=your-strong-password-here

CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
  

Generate the application key:

  php artisan key:generate
  

Set Permissions

Keep the code owned by your user so git pull, composer, and artisan keep working, and give the web server write access only to the two folders Laravel writes to. ACLs let both your user and www-data write there, including files created later:

  sudo apt install acl -y
sudo setfacl -R -m u:www-data:rwX -m u:$USER:rwX storage bootstrap/cache
sudo setfacl -dR -m u:www-data:rwX -m u:$USER:rwX storage bootstrap/cache
  

Run Migrations and Caching

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

Step 6: Configure Nginx

Create an Nginx server block:

  sudo nano /etc/nginx/sites-available/laravel-app
  
  server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    root /var/www/laravel-app/public;

    index index.php index.html;

    charset utf-8;

    # Handle Laravel routes
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # PHP processing
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_hide_header X-Powered-By;
    }

    # Block access to dotfiles
    location ~ /\.(?!well-known).* {
        deny all;
    }

    # Cache static assets
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2|woff|ttf)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}
  

Enable the site:

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

# Remove default site if this is the primary app
sudo rm -f /etc/nginx/sites-enabled/default

sudo nginx -t
sudo systemctl reload nginx
  

Step 7: Install SSL

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

Test auto-renewal:

  sudo certbot renew --dry-run
  

Step 8: Set Up Queue Workers

If your app uses queues (email, jobs, notifications), set up a Supervisor process:

  sudo apt install supervisor -y
  

Create a worker config:

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

Start the workers:

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

Step 9: Set Up the Scheduler

Laravel’s task scheduler needs a cron entry:

  sudo crontab -u www-data -e
  

Add:

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

Deploying Updates

When you push new code, run this on the server:

  cd /var/www/laravel-app

# Pull latest code
git pull origin main

# Install/update dependencies
composer install --optimize-autoloader --no-dev

# Run migrations
php artisan migrate --force

# Clear and rebuild caches
php artisan config:cache
php artisan route:cache
php artisan view:cache

# Reload PHP-FPM (needed when opcache.validate_timestamps=0, see Performance Tuning)
sudo systemctl reload php8.4-fpm

# Restart queue workers
sudo supervisorctl restart laravel-worker:*
  

You can wrap this in a deploy script:

  nano /var/www/deploy-laravel.sh
  
  #!/bin/bash
set -e

cd /var/www/laravel-app

echo "Pulling latest code..."
git pull origin main

echo "Installing dependencies..."
composer install --optimize-autoloader --no-dev

echo "Running migrations..."
php artisan migrate --force

echo "Rebuilding caches..."
php artisan config:cache
php artisan route:cache
php artisan view:cache

echo "Reloading PHP-FPM..."
sudo systemctl reload php8.4-fpm

echo "Restarting workers..."
sudo supervisorctl restart laravel-worker:*

echo "Deploy complete."
  
  chmod +x /var/www/deploy-laravel.sh
  

For automated deployments via GitHub, see Deploy from GitHub to Your Server.

Performance Tuning

PHP-FPM Settings

For a VPS with 4GB RAM, tune the pool:

  sudo nano /etc/php/8.4/fpm/pool.d/www.conf
  
  pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10
  

Restart:

  sudo systemctl restart php8.4-fpm
  

OPcache

OPcache should be enabled by default. Verify:

  php -i | grep opcache.enable
  

If you need to tune it:

  sudo nano /etc/php/8.4/fpm/conf.d/10-opcache.ini
  
  opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
  

Setting validate_timestamps=0 means PHP won’t check if files have changed. This is faster, but PHP keeps serving the old code until PHP-FPM is reloaded, which is why both deploy sequences above end with:

  sudo systemctl reload php8.4-fpm
  

Redis

Redis was installed in Step 4 and handles caching, sessions, and queues. See How to Set Up Redis Caching for the full configuration.

Troubleshooting

ProblemFix
500 error after deployCheck storage/logs/laravel.log. Usually a missing .env key or failed migration
“Permission denied” on storageRe-run the two setfacl commands from Set Permissions
“Class not found”Run composer dump-autoload
Blank page with no errorsSet APP_DEBUG=true temporarily to see the error, then turn it off
Nginx shows default pageCheck server_name matches your domain. Verify the symlink in sites-enabled
Queue jobs not processingCheck Supervisor: sudo supervisorctl status. Check the worker log
Slow response timesEnable OPcache, use Redis for sessions/cache, check database query performance

Last updated 21 Oct 2026, 00:00 +0300. history

Was this page helpful?