Deploying Laravel on a VPS means preparing a Linux server, installing a PHP runtime that matches your Laravel major version, configuring Nginx to serve the application’s public directory, connecting a database, and setting up the workers and TLS that production needs. This guide uses the common Ubuntu, Nginx and PHP-FPM stack and includes both a straightforward in-place deployment and a release-directory approach for minimal downtime.
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
What you need before deploying
Have these items ready before connecting to the server:
As an Amazon Associate I earn from qualifying purchases.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →- A VPS running a supported Linux distribution. The commands below assume Ubuntu and a sudo-enabled account.
- A domain name whose DNS can point an A or AAAA record at the VPS address.
- Your Laravel application in a Git repository, including a reliable
composer.lockfile. - Database credentials and a database server. MySQL and PostgreSQL are common choices; the PHP driver must match the database you select.
- The PHP version required by your Laravel major version. Laravel 12 requires PHP 8.2 or newer, while Laravel 13 requires PHP 8.3 or newer.
Do not copy a local development .env file to a public repository. Production secrets should be created on the server and readable only by the deployment account and the processes that need them.
Choose the PHP version and required extensions
PHP and Laravel must be selected as a pair. The Laravel 12.x requirements specify PHP 8.2 or higher; Laravel 13.x raises the minimum to PHP 8.3. Both versions require these extensions:
#1 Best Overall
| Requirement | Laravel 12 | Laravel 13 |
|---|---|---|
| Minimum PHP | 8.2 | 8.3 |
| Required extensions | Ctype, cURL, DOM, Fileinfo, Filter, Hash, Mbstring, OpenSSL, PCRE, PDO, Session, Tokenizer and XML | |
Install the database driver for your connection as well, such as php8.3-mysql for MySQL or php8.3-pgsql for PostgreSQL. If your application handles images, internationalization or Redis queues, install the corresponding extensions used by that application. Keep the PHP-FPM package and command-line PHP package on the same major and minor version so Composer and web requests load compatible extensions.
Install the VPS software
Update the operating system
sudo apt update
sudo apt upgrade -y
Create or use a non-root deployment account with sudo access. Run application commands as that account rather than as root.
Install Nginx, PHP-FPM, Git and supporting packages
Replace the PHP version in the example with the version required by your Laravel application.
sudo apt install -y nginx git unzip curl
php8.3-fpm php8.3-cli php8.3-common php8.3-mbstring
php8.3-xml php8.3-curl php8.3-zip php8.3-mysql
php8.3-bcmath php8.3-intl php8.3-opcache
The exact package names vary by Ubuntu release and database. Confirm that every extension listed by your Laravel version is enabled before continuing:
php -v
php -m
Start the services and configure them to start after a reboot:
sudo systemctl enable --now nginx
sudo systemctl enable --now php8.3-fpm
Install Composer
Install Composer using its official installer, verify the installer as recommended by Composer, and place the executable somewhere on the system path. A typical global installation ends with:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
sudo mv composer.phar /usr/local/bin/composer
composer --version
Use the same deployment account for Composer that will own the application checkout. This prevents a later deploy from failing because vendor files belong to root.
Rank #2
Prepare the database
Create an empty production database and a dedicated database user. Grant that user access only to the application database, and keep the password out of shell history where practical. The database host may be 127.0.0.1 when the database runs on the VPS, or a private hostname supplied by a managed database service.
Record the values you will place in .env: DB_CONNECTION, DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME and DB_PASSWORD. Test the credentials with the database client before blaming Laravel if migrations cannot connect.
Deploy the application code
Clone into a stable application directory
sudo mkdir -p /var/www/example-app
sudo chown -R "$USER":www-data /var/www/example-app
cd /var/www/example-app
git clone https://your-git-host.example/your-team/your-app.git .
Use your repository’s real clone URL in place of the example. For production, check out a specific release tag or commit rather than deploying an unreviewed branch tip.
Create the production environment file
cp .env.example .env
php artisan key:generate
Edit .env and set at least the application environment, URL, database connection, cache and queue settings:
APP_ENV=production
APP_DEBUG=false
APP_URL=https://app.example.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=example_app
DB_USERNAME=example_app
DB_PASSWORD=use-a-long-secret
CACHE_STORE=file
QUEUE_CONNECTION=database
Choose cache and queue drivers that your application actually supports. If you use Redis, configure its host, port and password and install the PHP Redis extension. Never enable APP_DEBUG on an Internet-facing production site: exception pages can disclose configuration and credentials.
Install production dependencies
composer install --no-dev --optimize-autoloader
This installs the locked dependency set without development packages and builds an optimized autoloader. If Composer reports a platform requirement error, fix the PHP version or missing extension rather than bypassing the check.
Set writable directories
Laravel must be able to write compiled views, logs, sessions, file cache and framework cache files. The two directories that must be writable are storage and bootstrap/cache.
Free tools Windows power users keep installed
One-click scans. No signup required.
sudo chown -R "$USER":www-data storage bootstrap/cache
sudo chmod -R ug+rwx storage bootstrap/cache
Keep the rest of the project read-only to the web-service account where possible. If a deployment tool changes ownership, make sure the account running Artisan can still read the application and write those two directories.
Run migrations and build Laravel caches
Apply the schema
php artisan migrate --force
The --force flag allows migrations to run when Laravel identifies the environment as production. Review destructive or backward-incompatible migrations before running them. When a migration changes a column that old workers still use, deploy compatible code first or use a maintenance window.
Cache the framework configuration
Laravel’s production deployment guidance recommends caching configuration, events, routes and views. The combined command is:
php artisan optimize
After changing environment values, clear stale generated files before rebuilding them:
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →php artisan optimize:clear
php artisan optimize
Run these commands after dependencies and the production .env are in place. If configuration appears stuck on an old value, this clear-and-rebuild sequence is the first check.
Configure Nginx to serve Laravel safely
Nginx must point at Laravel’s public directory, not the project root. The try_files rule serves existing assets and sends other requests to public/index.php. FastCGI then passes PHP requests to PHP-FPM.
server {
listen 80;
listen [::]:80;
server_name app.example.com;
root /var/www/example-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;
}
location ~ /. {
deny all;
}
}
Change the domain, project path and PHP-FPM socket to match your server. The dot-file rule blocks requests for files such as .env. Do not put uploads, private exports or backup archives inside a web-accessible directory.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsRank #4
Enable the site and remove the default server if it would otherwise answer for your domain:
sudo ln -s /etc/nginx/sites-available/example-app /etc/nginx/sites-enabled/example-app
sudo nginx -t
sudo systemctl reload nginx
nginx -t must succeed before every reload. A 404 for every route usually means the root is not the Laravel public directory or the try_files fallback is missing. A 502 generally means PHP-FPM is stopped, the socket path is wrong or the FPM process cannot read the project.
Add HTTPS with Let’s Encrypt
Install Certbot’s Nginx integration and request a certificate for the domain that already resolves to the VPS:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d app.example.com
Certbot can update the Nginx server block and install a renewal timer. Confirm that renewal is scheduled and that the HTTPS virtual host redirects plain HTTP when appropriate. Update APP_URL to the HTTPS address and reload Nginx after certificate or configuration changes.
Recommended Free Tools
Keep queue workers running
A web request does not process queued jobs unless a worker is running. Use Supervisor to keep php artisan queue:work (or Laravel Horizon) alive, restart failed workers and start them after a reboot.
Install Supervisor
sudo apt install -y supervisor
sudo systemctl enable --now supervisor
Create a worker program
[program:example-app-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/example-app/artisan queue:work --sleep=3 --tries=3 --timeout=90
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=deploy
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/example-app/storage/logs/worker.log
stopwaitsecs=3600
Save the file under /etc/supervisor/conf.d/example-app-worker.conf. Replace deploy, the path and worker options with your account and workload. The timeout must be longer than the longest job, and your queue backend must be configured in .env.
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status
Restart workers after every release
Long-lived workers keep the old application code in memory. After deploying new code, tell Laravel to let workers finish their current job and exit:
Best Value
php artisan queue:restart
Supervisor starts replacement workers. Run this command after the new release is active, not before, so replacements load the intended code. Horizon deployments use the same principle, with Horizon’s process controls replacing the plain worker command.
Outdated 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 matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Use a simple in-place deployment safely
For a small application that can tolerate a brief maintenance window, deploy in this order:
- Put the site in maintenance mode if a migration or file change cannot be served by the current code:
php artisan down. - Fetch the intended commit or tag with Git.
- Run
composer install --no-dev --optimize-autoloader. - Check ownership and write permissions on
storageandbootstrap/cache. - Run
php artisan migrate --forcewhen the schema change is backward compatible. - Run
php artisan optimize. - Reload PHP-FPM if your process manager or deployment process requires it.
- Run
php artisan queue:restartso workers load the release. - Bring the site back with
php artisan upand check an authenticated page, a public asset and a queued job.
Do not run migrations automatically on every web request. Keep them as an explicit deployment step so a failed migration stops the release instead of leaving the application half-updated.
Use release directories for minimal downtime
A symlink-based layout separates immutable releases from shared data. It also gives you a fast way to point Nginx back to the previous release if a post-deploy check fails.
/var/www/example-app/
├── current -> releases/20261014120000
├── releases/
│ ├── 20261014120000/
│ └── 20261014123000/
└── shared/
├── .env
└── storage/
- Create a timestamped directory under
releasesand clone the exact commit into it. - Link
shared/.envto the release’s.envand linkshared/storageto the release’sstoragedirectory. - Run Composer in the new release, then verify the writable cache path.
- Run migrations from the new release. Keep schema changes compatible with both the old and new code when there is no maintenance window.
- Run
php artisan optimizefrom the new release. - Atomically replace the
currentsymlink with the new release. - Reload PHP-FPM if needed, then run
php artisan queue:restartfrom the new release. - Check the application, logs and worker status before removing older releases.
Point Nginx at /var/www/example-app/current/public when using this layout. Keep at least one known-good previous release until the new one has passed its checks. If a migration is not backward compatible, use a planned maintenance window with php artisan down and php artisan up rather than pretending a symlink swap alone can prevent errors.
Troubleshoot the first production request
“Class or extension not found” during Composer or Artisan
- Run
php -vandphp -mas the deployment user. - Confirm the CLI PHP version matches the PHP-FPM version serving Nginx.
- Install the missing Laravel-required extension or the database driver, then restart PHP-FPM.
HTTP 500 or “failed to open stream” for logs and cache
- Check that
storageandbootstrap/cacheare writable by the account running PHP-FPM. - Inspect Laravel’s log under
storage/logsand the Nginx and PHP-FPM service logs. - Run
php artisan optimize:clear, correct.env, then rebuild withphp artisan optimize.
Routes work locally but return 404 through Nginx
- Verify the Nginx
rootends in/public. - Verify the
try_filesfallback points to/index.php?$query_string. - Run
sudo nginx -tand reload Nginx after correcting the server block.
HTTP 502 Bad Gateway
- Check
sudo systemctl status php8.3-fpm. - Compare the
fastcgi_passsocket with the file present under/run/php/. - Make sure PHP-FPM can traverse and read the project directory.
Jobs still execute old code
Run php artisan queue:restart after the release and confirm Supervisor reports replacement processes as running. A worker that never exits may be stuck in a job, have an excessive timeout or be unable to write its log.
Production deployment checklist
- PHP meets the Laravel major version minimum: 8.2+ for Laravel 12 or 8.3+ for Laravel 13.
- All required PHP extensions and the selected database driver are enabled.
- Nginx serves only the application’s
publicdirectory and denies dot-files. APP_ENV=production,APP_DEBUG=falseand a correct HTTPSAPP_URLare set.- The production database is reachable with a dedicated user.
storageandbootstrap/cacheare writable, while secrets and the project root are not publicly downloadable.- Dependencies were installed with
--no-dev --optimize-autoloader. - Migrations ran with an intentional rollback or maintenance plan.
- Laravel caches were rebuilt with
php artisan optimize. - Let’s Encrypt renewal is scheduled and HTTPS serves the real domain.
- Supervisor keeps workers running, and
php artisan queue:restartis part of every deployment. - A post-deploy check covers a web request, static asset, database read/write and queued job.
Alternative process models
Nginx with PHP-FPM gives explicit control over virtual hosts, PHP workers and operating-system services, which is why it is a practical default for a single VPS. Laravel’s 12.x documentation also describes running the application through FrankenPHP with its php-server command pointed at public/. That option changes the process model, but it does not remove the requirements for a production environment file, writable Laravel directories, migrations, cache building, TLS and worker supervision. Containerized or managed deployment systems can automate these steps; verify that they still provide persistent storage, repeatable releases and a deliberate queue-worker restart.
Quick 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.

