Deploying Laravel on a VPS means preparing a production Linux server, installing a PHP version supported by your Laravel release, configuring Nginx to serve the application’s public directory, and running the release with production-safe settings. The reliable sequence is: provision Ubuntu, install PHP-FPM and required extensions, install Composer and a database, deploy the code, create and protect .env, set write permissions for storage and bootstrap/cache, run migrations and Laravel caches, add HTTPS, and keep queue workers supervised.
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
This guide uses the conventional Ubuntu plus Nginx plus PHP-FPM stack. It also shows how to choose PHP for Laravel 12 or 13, configure workers, recover from common failures, and move from a simple in-place deployment to a symlink-based release process.
As an Amazon Associate I earn from qualifying purchases.
Choose the PHP and Laravel versions first
PHP must meet the minimum required by the Laravel major version you deploy. Laravel 12 requires PHP 8.2 or newer; Laravel 13 raises the minimum to PHP 8.3. Both releases require the same core extensions: Ctype, cURL, DOM, Fileinfo, Filter, Hash, Mbstring, OpenSSL, PCRE, PDO, Session, Tokenizer, and XML. Install a PHP minor release that your operating-system packages and application dependencies support, then keep that version consistent across the command-line binary and PHP-FPM.
| Laravel release | Minimum PHP | Practical VPS choice | Important qualification |
|---|---|---|---|
| 12.x | 8.2 | PHP 8.2 or 8.3 with the matching FPM service | The minimum is from Laravel’s 12.x deployment requirements; packages and extensions still have to be available for your Ubuntu release. |
| 13.x | 8.3 | PHP 8.3 or newer supported by your dependency lock file | Do not run a Laravel 13 application on PHP 8.2, even if another application on the same VPS does. |
Check the project’s composer.json and composer.lock before provisioning. A lock file generated with a different PHP platform can cause Composer to reject the server or select an unexpected dependency set.
#1 Best Overall
Prepare the VPS and a deployment account
Use a current Ubuntu LTS image from your VPS provider. Create a non-root account for deployments, grant it administrative access through sudo, and use SSH keys rather than password login. Replace the example account and hostname values below with your own.
- Connect and update packages.
ssh root@SERVER_IP apt update && apt full-upgrade -y - Create an administrator account.
adduser deploy usermod -aG sudo deploy - Install basic tools.
apt install -y git unzip curl ca-certificates software-properties-common ufw - Enable a basic firewall policy.
ufw allow OpenSSH ufw allow 'Nginx Full' ufw enable ufw status - Copy your SSH key to the new account and test it.
ssh-copy-id deploy@SERVER_IP ssh deploy@SERVER_IP
Only after testing the new account should you disable direct root SSH access and password authentication in /etc/ssh/sshd_config. Validate the configuration with sshd -t, then reload SSH. Keep your current session open until a second key-based session succeeds.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Install Nginx, PHP-FPM, and Laravel extensions
Nginx serves static files and forwards PHP requests to PHP-FPM. Install the FPM package whose version matches the Laravel requirement. The package names below use PHP 8.3; substitute 8.2 when deploying Laravel 12 on a PHP 8.2 system.
sudo apt install -y nginx
php8.3-fpm php8.3-cli php8.3-common
php8.3-curl php8.3-mbstring php8.3-xml php8.3-zip
php8.3-bcmath php8.3-intl php8.3-gd php8.3-mysql
php8.3-opcache
If you use PostgreSQL, install php8.3-pgsql instead of (or in addition to) the MySQL driver. If your application uses Redis, install a Redis PHP extension supported by your chosen PHP version. Laravel’s required list is the baseline; package additions such as intl, zip, a database driver, GD, or Redis depend on your application.
Confirm that the CLI and FPM versions agree:
php -v
php -m
systemctl status php8.3-fpm --no-pager
sudo systemctl enable --now nginx php8.3-fpm
For production, enable OPcache and size it according to the application and available memory. Make configuration changes in the PHP-FPM and OPcache configuration files supplied by your distribution, then restart FPM and verify that the service returns to active (running).
Install and verify the database
Choose the database engine declared in .env. A local database reduces network latency but means database backups and upgrades are your responsibility. A managed database can isolate failure and backups, but requires a private network path, firewall rules, and credentials that are not exposed publicly.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →MySQL or MariaDB on the VPS
sudo apt install -y mysql-server
sudo mysql_secure_installation
Create a database and a user with a strong, unique password. Grant that user access only to the application database; do not use the database administrator account in Laravel. Test the credentials from the server before deploying the application.
PostgreSQL
sudo apt install -y postgresql postgresql-contrib
Create a dedicated role and database, then set DB_CONNECTION=pgsql and the corresponding host, port, database, username, and password in the production environment file.
Install Composer safely
Composer is Laravel’s dependency manager. Install it with Composer’s official installer, verify the installer signature as described by Composer’s documentation, and place the binary somewhere on the system path:
curl -sS https://getcomposer.org/installer -o composer-setup.php
php composer-setup.php --install-dir=/usr/local/bin --filename=composer
rm composer-setup.php
composer --version
Run Composer as the deployment user, not as root. Installing dependencies as root often leaves a tree of root-owned files that the web process cannot update or read during a later release.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteDeploy the Laravel code
Keep application releases outside the web server’s default document root. A simple layout is /var/www/example with the current release in current; a release-based layout is better for rollback and is covered later.
- Create the directory and assign ownership.
sudo mkdir -p /var/www/example sudo chown -R deploy:www-data /var/www/example - Clone the repository as the deployment user.
cd /var/www/example git clone --depth 1 --branch main GIT_REPOSITORY current cd current - Install production dependencies.
composer install --no-dev --optimize-autoloader - Create the production environment file.
cp .env.example .env php artisan key:generate --force
Do not commit production secrets. Set APP_ENV=production, APP_DEBUG=false, the exact APP_URL, database values, mail transport, cache and queue drivers, and any third-party credentials directly on the server or through a secret-management system. Protect .env so only the deployment user and the web-service group can read it.
If the repository contains frontend assets, install the project’s pinned Node.js version and run its documented production build before switching traffic. Keep generated assets in the release directory and ensure Nginx can read them.
Set Laravel’s required permissions
Laravel must be able to write logs, compiled views, sessions, and framework cache files. The two directories that must be writable are storage and bootstrap/cache. Make the deployment user the owner and grant the web-service group access without making the entire application world-writable.
Recommended Free Tools
Rank #3
sudo chown -R deploy:www-data /var/www/example/current
sudo find /var/www/example/current -type d -exec chmod 755 {} ;
sudo find /var/www/example/current -type f -exec chmod 644 {} ;
sudo chmod -R ug+rwx /var/www/example/current/storage
sudo chmod -R ug+rwx /var/www/example/current/bootstrap/cache
If workers run under a separate account, add that account to the group that owns these directories or adjust the shared-release permissions. A “permission denied” exception for a log, cache file, or compiled view nearly always indicates ownership or mode drift after a deploy.
Configure Nginx to expose only public
Never point Nginx at the repository root. The document root must be Laravel’s public directory, which keeps source files, the environment file, and Composer metadata outside the directly served path.
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example/current/public;
index index.php index.html;
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;
}
location ~ /. {
deny all;
}
location = /.env {
deny all;
}
}
Save the site as /etc/nginx/sites-available/example, enable it, disable the default site if it conflicts, and test before reloading:
sudo ln -s /etc/nginx/sites-available/example /etc/nginx/sites-enabled/example
sudo nginx -t
sudo systemctl reload nginx
The try_files fallback sends routes that are not physical files to index.php. The dot-file rule prevents accidental exposure of Git metadata and other hidden files; the explicit .env rule makes the intent clear. If the site returns a 502 error, check that the socket path matches the installed PHP-FPM version and inspect journalctl -u php8.3-fpm.
Run migrations and production caches
Before migrating, verify that the application can reach the database and that the backup policy is in place. Laravel’s production migration command requires an explicit force flag because migrations can alter or remove data:
php artisan migrate --force
Laravel’s deployment guidance recommends caching configuration, events, routes, and views in production. The single optimization command creates those caches:
php artisan optimize
When diagnosing stale configuration or route behavior, clear generated caches with:
Rank #4
php artisan optimize:clear
Run cache-clearing only when you understand the effect: a busy application may briefly regenerate files, and a deployment that clears caches without rebuilding them can increase first-request latency. After changing .env, rebuild the caches before sending traffic to the new release.
Keep queue workers running with Supervisor
A web request does not process queued jobs unless a worker is running. Supervisor starts workers at boot, restarts them after a crash, and keeps the process count under control. Install it from the distribution packages:
sudo apt install -y supervisor
sudo systemctl enable --now supervisor
Create /etc/supervisor/conf.d/laravel-worker.conf:
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/example/current/artisan queue:work --sleep=3 --tries=3 --timeout=90
directory=/var/www/example/current
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=deploy
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/example/current/storage/logs/worker.log
stopwaitsecs=3600
Choose numprocs, timeout, retry count, and queue names for your workload. Long-running jobs need a worker timeout shorter than Supervisor’s stop wait, and the queue’s visibility timeout must exceed the worker timeout so a job is not delivered twice while still running.
Load and inspect the program:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status laravel-worker:*
Every deployment must tell existing workers to finish their current job and reload the new code. Run this after the new release is ready:
Free tools Windows power users keep installed
One-click scans. No signup required.
php artisan queue:restart
sudo supervisorctl restart laravel-worker:*
queue:restart signals workers to exit after the current job; Supervisor then starts them with the new release. If you use Laravel Horizon, supervise the Horizon process instead and follow the same release-restart principle.
Add HTTPS with Let’s Encrypt
Point your DNS A or AAAA records at the VPS, confirm that ports 80 and 443 are reachable, and install Certbot with its Nginx integration:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
Certbot can install the certificate and update the Nginx server block. Test renewal rather than waiting for expiration:
Best Value
sudo certbot renew --dry-run
After TLS is active, set APP_URL to the HTTPS URL and configure Laravel’s trusted proxy behavior if TLS terminates at a load balancer or CDN. Mixed-content warnings usually mean an asset URL or application URL still uses HTTP.
Use a release-and-symlink deployment for safer updates
An in-place git pull is easy but exposes a partially updated codebase while Composer, assets, or migrations run. A release layout makes the switch atomic:
- Create
/var/www/example/releases/20261014090000and clone the exact commit into it. - Link the shared environment file and persistent storage:
.envshould point to/var/www/example/shared/.env, andstorageshould point to/var/www/example/shared/storage. - Run
composer install --no-dev --optimize-autoloader, build frontend assets, set permissions, and run checks without serving the release. - Run database migrations. Use backward-compatible migrations when old and new code overlap; if a migration cannot be compatible, schedule a maintenance window and use
php artisan downbefore it, thenphp artisan upafter validation. - Run
php artisan optimize, then atomically replace thecurrentsymlink with the new release. - Reload PHP-FPM if required by your opcode configuration and run
php artisan queue:restart. Supervisor will start workers against the new path. - Keep several previous releases until health checks pass, then remove old releases without deleting the shared storage.
Nginx continues to use /var/www/example/current/public; only the symlink target changes. Rollback is a symlink switch to the previous release, followed by a worker restart. Database rollbacks are a separate decision: code can usually be reverted quickly, but destructive schema changes may not be reversible.
Verify the deployment before announcing it
- Open the HTTPS homepage and a representative authenticated route.
- Check
storage/logs/laravel.logfor configuration, database, and permission errors. - Run
php artisan aboutif available in your Laravel version to inspect environment and cache state without exposing secrets. - Confirm
php artisan migrate:statusshows the expected migrations. - Submit a test job and watch
supervisorctl statusand the worker log. - Verify scheduled tasks. If using Laravel’s scheduler, configure one system cron entry for the deployment user:
* * * * * cd /var/www/example/current && php artisan schedule:run >> /dev/null 2>&1 - Check certificate expiry and run the Certbot dry-run periodically.
- Confirm backups can be restored, not merely created.
Troubleshooting the failures seen most often
502 Bad Gateway
Nginx cannot reach PHP-FPM. Check systemctl status php8.3-fpm, confirm the fastcgi_pass socket exists under /run/php, and inspect Nginx and FPM logs. A PHP version mismatch between the installed service and the configured socket is the usual cause.
500 error after deployment
Read the Laravel log, then verify APP_KEY, database credentials, PHP extensions, and cached configuration. Run php artisan optimize:clear, correct the environment, and rebuild with php artisan optimize. Ensure the web user can write to storage and bootstrap/cache.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Routes return 404 but the homepage works
The Nginx try_files fallback is missing or points to the wrong front controller. Confirm the root ends in /public, reload only after nginx -t passes, and check that the route exists in the deployed code.
Jobs keep running old code
Workers are long-lived and do not automatically reload PHP files. Run php artisan queue:restart, then verify Supervisor starts workers from the current release. In a symlink deployment, ensure the worker command references the stable current path rather than a retired timestamped directory.
Composer refuses to install
Compare php -v with the Laravel and Composer platform requirements, install the missing extension named in the error, and run Composer as the deployment user. Do not bypass platform checks on production; they indicate that the server does not match the application’s declared requirements.
Production hardening checklist
- Set
APP_DEBUG=falseand use a unique application key. - Keep
.env, Git metadata, and Composer files outside the Nginx document root. - Use least-privilege database credentials and restrict database network access.
- Allow only required firewall ports, normally SSH, HTTP, and HTTPS.
- Patch Ubuntu, PHP, Nginx, the database, and Composer dependencies on a scheduled maintenance cycle.
- Rotate exposed credentials and revoke old SSH keys promptly.
- Monitor disk usage, especially logs, uploaded files, database dumps, and failed queue jobs.
- Back up the database and user uploads separately from the VPS, and periodically perform a restore test.
- Document the PHP-FPM socket, Supervisor program, deployment commit, migration policy, and rollback path so another operator can recover the service.
The finished deployment has a narrow Nginx entry point, a PHP runtime that matches Laravel, immutable production dependencies, writable framework directories, cached application metadata, encrypted transport, and workers that are explicitly restarted whenever code changes. That combination is what turns a cloned repository into a maintainable Laravel production service on a VPS.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchQuick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




