This guide shows how to deploy Laravel on a VPS the standard way: Nginx serving the public folder, PHP-FPM running the code, Composer for dependencies, and systemd keeping queue workers alive. Commands are for Ubuntu 22.04/24.04; AlmaLinux differs mainly in package names.
Before you start
- A VPS with sudo access and a domain pointing to it.
- A database (MySQL/MariaDB or PostgreSQL) and its credentials.
- Your Laravel app in a Git repository.
- For cPanel hosting, follow the cPanel Laravel guide instead.
Install PHP, Nginx and Composer
apt update && apt install nginx git unzip php8.3-fpm php8.3-cli php8.3-mysql php8.3-mbstring php8.3-xml php8.3-curl php8.3-zip php8.3-bcmath php8.3-intl php8.3-gdCheck your Laravel version's PHP requirement first; install a newer PHP from the ondrej PPA if needed. Install Composer:
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composerGet the code
mkdir -p /var/www/app && chown deploy:www-data /var/www/appsudo -u deploy git clone git@github.com:you/app.git /var/www/appcd /var/www/app && sudo -u deploy composer install --no-dev --optimize-autoloaderConfigure .env
sudo -u deploy cp .env.example .env && sudo -u deploy php artisan key:generateEdit .env:
APP_ENV=production
APP_DEBUG=false
APP_URL=https://example.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=app
DB_USERNAME=app
DB_PASSWORD=strong-passwordDanger: never run production with APP_DEBUG=true. Error pages would reveal your environment, including database passwords.
Run migrations and cache the config:
php artisan migrate --force && php artisan config:cache && php artisan route:cache && php artisan view:cacheSet permissions
Only storage and bootstrap/cache must be writable by PHP:
chown -R deploy:www-data storage bootstrap/cache && chmod -R ug+rwX storage bootstrap/cacheCreate the public storage link if the app uses it: php artisan storage:link.
Configure Nginx
Create /etc/nginx/sites-available/app:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/app/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
}
location ~ /\.(?!well-known) {
deny all;
}
}The root must point at public, never the project root, or .env could be downloaded. Enable it:
ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/ && nginx -t && systemctl reload nginxAdd SSL: apt install certbot python3-certbot-nginx && certbot --nginx -d example.com -d www.example.com.
Run queue workers with systemd
Create /etc/systemd/system/laravel-worker.service:
[Unit]
Description=Laravel queue worker
After=network.target
[Service]
User=deploy
Group=www-data
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/php artisan queue:work --sleep=3 --tries=3 --max-time=3600
Restart=always
[Install]
WantedBy=multi-user.targetsystemctl daemon-reload && systemctl enable --now laravel-workerEnable the scheduler
Add one cron entry for the deploy user (crontab -u deploy -e):
* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1Deploy updates
cd /var/www/app && php artisan down && git pull && composer install --no-dev --optimize-autoloader && php artisan migrate --force && php artisan optimize && php artisan up && systemctl restart laravel-worker && systemctl reload php8.3-fpmReloading PHP-FPM clears OPcache so new code is served; restarting workers loads the new code into queues.
Troubleshooting
- 500 error with a blank page: read
storage/logs/laravel.log. Most often it is permissions onstorageor a missingAPP_KEY. - The stream or file laravel.log could not be opened: PHP-FPM (www-data) cannot write
storage. Fix ownership as above. - Config changes ignored: the config is cached. Run
php artisan config:clearor cache again. - 404 on every route except /: the
try_filesline is missing. - Jobs not processed: check
systemctl status laravel-workerandQUEUE_CONNECTIONin .env.
Laravel deployment FAQ
Do I need Laravel Forge or Envoyer?
No. They automate these same steps; doing it by hand once helps you understand and debug them.
Can Laravel run on cPanel hosting?
Yes. Point the domain's document root to public and use the cPanel Laravel guide.
Should I use Redis for cache and queues?
For busy apps, yes: set CACHE_STORE=redis and QUEUE_CONNECTION=redis after installing Redis and the PHP extension.
