How to Deploy a Laravel App on a VPS
Install PHP, Composer, and Nginx on your GoZen VPS and deploy a Laravel application with SSL, database, queues, and a zero-downtime workflow.
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
- A GoZen VPS or dedicated server with initial setup with Ubuntu 22.04+
- SSH access to your server
- A domain pointed to your server IP (how to point a domain)
- Your Laravel project in a Git repository
Step 1: Install PHP and Required Extensions
Laravel needs PHP 8.2+ and several extensions:
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.3 php8.3-fpm php8.3-cli php8.3-mysql php8.3-pgsql \
php8.3-mbstring php8.3-xml php8.3-curl php8.3-zip php8.3-bcmath \
php8.3-gd php8.3-intl php8.3-redis php8.3-opcache -y
Verify the install:
php -v
# Should show PHP 8.3.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-contriband adjust the commands accordingly.
Step 4: Install Nginx
sudo apt install nginx -y
sudo systemctl enable nginx
sudo systemctl start nginx
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 git@github.com: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_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
Generate the application key:
php artisan key:generate
Set Permissions
sudo chown -R www-data:www-data /var/www/laravel-app
sudo chmod -R 775 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.3-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
# 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 "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.3/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.3-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.3/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 requires you to restart PHP-FPM after each deploy:
sudo systemctl restart php8.3-fpm
Redis
If you’re using Redis for caching, sessions, and queues (recommended):
sudo apt install redis-server -y
sudo systemctl enable redis-server
See How to Set Up Redis Caching for the full configuration.
Troubleshooting
| Problem | Fix |
|---|---|
| 500 error after deploy | Check storage/logs/laravel.log. Usually a missing .env key or failed migration |
| “Permission denied” on storage | Run sudo chown -R www-data:www-data storage bootstrap/cache |
| “Class not found” | Run composer dump-autoload |
| Blank page with no errors | Set APP_DEBUG=true temporarily to see the error, then turn it off |
| Nginx shows default page | Check server_name matches your domain. Verify the symlink in sites-enabled |
| Queue jobs not processing | Check Supervisor: sudo supervisorctl status. Check the worker log |
| Slow response times | Enable OPcache, use Redis for sessions/cache, check database query performance |
Related Articles
Last updated 19 Apr 2026, 23:46 +0300.