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 repeatable deploy workflow.
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
- A GoZen VPS or dedicated server running Ubuntu 22.04+ that has completed initial setup
- 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
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-contriband 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
| Problem | Fix |
|---|---|
| 500 error after deploy | Check storage/logs/laravel.log. Usually a missing .env key or failed migration |
| “Permission denied” on storage | Re-run the two setfacl commands from Set Permissions |
| “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 21 Oct 2026, 00:00 +0300.