Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Laravel Production Deployment Checklist

By Kokil Thapa | Last reviewed: September 2026

You ship a feature locally, run tests, and merge. Then production breaks on a missing .env key, a stale opcache file, or a queue worker still running old code. A repeatable Laravel production deployment checklist closes that gap. It turns release day from guesswork into a short sequence you can run on every project—from a single VPS in Kathmandu to a multi-server stack. This guide covers pre-flight checks, server layout, zero-downtime releases, and post-deploy verification for Laravel applications in production.

What Should Be on Your Laravel Production Deployment Checklist Before You Deploy?

Pre-flight work prevents the failures that only appear after traffic hits the new release. Treat this block as non-negotiable. Skip it once and you will debug at midnight.

Environment and secrets

Production .env must never live in Git. Store it in a shared directory outside release folders when you use symlinked deploys. Confirm these keys at minimum:

  • APP_ENV=production and APP_DEBUG=false
  • APP_KEY set and identical across web nodes if you run more than one
  • Database, Redis, mail, and payment credentials verified against staging
  • APP_URL matching the canonical HTTPS domain
  • Queue and cache drivers pointing at production services, not sync or file

On client projects I maintain with Deployer 7 zero-downtime releases, the shared .env sits in /var/www/example.com/shared/.env. Each new release reads the same file. That one detail has prevented more incidents than any fancy tooling.

Version alignment

Match PHP and framework versions before you push. For new Laravel 13 projects, plan on PHP 8.3 or higher. Laravel 12 runs on PHP 8.2+. Many servers I administer still run PHP 8.4 alongside 8.5 during gradual upgrades.

# On the deploy runner and each app server
php -v
composer --version

# In your project
composer install --no-dev --optimize-autoloader --no-interaction
php artisan --version

Run composer install with --no-dev on production. Dev packages like Debugbar must never load in prod. Commit composer.lock and deploy that exact lock file.

Database and migration review

Read every pending migration before deploy. Destructive changes need a backup first. Long-running migrations may need a maintenance window or an online schema tool. For reporting-heavy apps, compare PostgreSQL vs MySQL for production before you alter large tables.

Pre-Deploy ReadinessCode mergedTests pass in CIEnv verifiedSecrets in sharedPHP aligned8.3+ for Laravel 13BackupDB + storageRelease artefact readycomposer.lock + built Vite assets + tagged commitGo / no-go decisionRollback plan documented before deploy starts
Laravel production deployment checklist: pre-flight gates before any release reaches the live server.

How Do You Prepare a Linux Server for Laravel Production?

Application code is only half the stack. The host must run PHP-FPM, a web server, a process supervisor, and scheduled tasks reliably. I deploy most Laravel sites on Ubuntu 22 or 24 with Apache or Nginx fronting PHP-FPM 8.3 or 8.4.

A typical Deployer layout keeps persistent data outside rotating release folders:

/var/www/example.com/
├── current -> releases/20250909120000/
├── releases/
│   ├── 20250909120000/
│   └── 20250908103000/
└── shared/
    ├── .env
    ├── storage/
    └── bootstrap/cache/

The web root must point at current/public, not the project root. Pointing at the repo root exposes .env if misconfigured. That mistake still appears on real audits.

Permissions and ownership

PHP-FPM runs as www-data on Ubuntu. Code can belong to the deploy user. Writable paths must belong to the web user or group:

sudo chown -R deploy:www-data /var/www/example.com/shared/storage
sudo chmod -R ug+rwx storage bootstrap/cache

After every deploy, confirm new release folders inherit correct ACLs. I have seen uploads fail silently because a fresh release directory lacked group write on storage/app.

Services you must configure

  1. PHP-FPM pool with opcache enabled and opcache.validate_timestamps=0 in production
  2. Web server vhost with HTTPS, HTTP/2, and security headers
  3. Supervisor for queue:work processes—see the Laravel queues with Redis production guide
  4. Cron entry: * * * * * cd /var/www/example.com/current && php artisan schedule:run
  5. Redis 8.x or Memcached 1.6.x for cache and sessions when traffic grows

For full server hardening—firewall, SSL renewal, log rotation—see Linux system administration for production Laravel hosts.

Production Server StackNginx / ApachePHP-FPM 8.4 / 8.5Laravel (current release)public/index.php + opcacheSupervisorqueue:work workersCronschedule:runRedis 8.xcache + queuesMySQL 9.7 / PostgreSQL 18 — persistent data layer
Standard Laravel production server topology: web server, PHP-FPM, workers, scheduler, cache, and database.

What Are the Exact Deploy Steps in a Laravel Production Deployment Checklist?

Order matters. Running config:cache before migrations, or forgetting to restart workers, leaves production in a split-brain state. Below is the sequence I run on Deployer-managed sites and GitLab CI pipelines—including sister legal-tech portals on shared EC2 infrastructure.

Build phase (CI runner or local)

composer install --no-dev --optimize-autoloader --no-interaction
npm ci
npm run build
php artisan test --parallel

Many production servers have no Node.js installed. Build Vite 8.x assets in CI and ship the public/build directory with the release. Committing built assets is boring and effective.

Release phase (on server)

  1. Clone or rsync code into releases/TIMESTAMP
  2. Link shared dirs: storage, .env, sometimes bootstrap/cache
  3. Run composer install --no-dev --optimize-autoloader
  4. Run php artisan migrate --force
  5. Run php artisan config:cache, route:cache, view:cache
  6. Run php artisan event:cache if you use it
  7. Atomically swap current symlink to the new release
  8. Reload PHP-FPM to clear opcache
  9. Restart Supervisor workers: php artisan queue:restart
  10. Prune old releases—keep three to five

Official guidance lives in the Laravel deployment documentation. Deployer recipes are documented at deployer.org.

Deployer deploy.php essentials

namespace Deployer;

require 'recipe/laravel.php';

set('application', 'example-app');
set('repository', 'git@gitlab.com:org/example.git');
set('deploy_path', '/var/www/example.com');
set('keep_releases', 5);

host('production')
    ->set('remote_user', 'deploy')
    ->set('hostname', '203.0.113.10')
    ->set('branch', 'main');

after('deploy:failed', 'deploy:unlock');

Sites like Adventure Third Pole Trek and several law-firm portals share this Deployer 7 + GitLab CI pattern. One pipeline lint, test, build, and deploy keeps releases predictable for small teams.

MethodBest forDowntimeComplexity
Manual FTP/rsyncLegacy fixes onlyMinutesLow
Deployer symlinkVPS, single/multi PHP nodesNear zeroMedium
Git pull on serverInternal tools, prototypesSeconds–minutesLow
Docker / KubernetesScaled API platformsZero with rolling updatesHigh

For container workflows, read the guide on Dockerizing a Laravel app for production. Most SMB clients in Nepal still land on a well-configured VPS first. That is often the right call on budget.

Zero-Downtime Release SequenceNew releasecomposer installMigrate--force flagCache buildconfig route viewSymlinkcurrent swapPHP-FPM reload + queue:restartWorkers pick up new code; opcache invalidatedSmoke tests passlogin, checkout, webhooksRollback if neededdep rollback to prior release
Zero-downtime Laravel production deployment: build release, migrate, cache, swap symlink, reload services, verify or rollback.

How Do You Handle Queues, Schedulers, and Background Jobs in Production?

Deploying code does not restart long-lived PHP processes. Queue workers cache the application bootstrap in memory. They keep serving old code until you restart them.

Supervisor configuration

[program:example-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/example.com/current/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopwaitsecs=3600
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/example.com/shared/storage/logs/worker.log

After deploy, run php artisan queue:restart. Supervisor respawns workers gracefully. Skipping this step is a classic cause of “fix deployed but emails still broken.”

Scheduler and Horizon

Confirm cron uses the current symlink path—not a hard-coded old release folder. I have fixed production schedulers where cron still pointed at a path from six months ago. Details sit in the Laravel scheduled tasks production setup article.

If you run Laravel Horizon for Redis queues, restart it during deploy the same way you restart standard workers. Treat Horizon as part of the release checklist, not optional tooling.

What Post-Deploy Checks Belong on a Laravel Production Deployment Checklist?

The symlink swap is not the finish line. Run structured verification while you can still roll back quickly.

Automated and manual smoke tests

  • HTTP 200 on homepage and one authenticated route
  • Login, password reset, and one write action (form submit or API POST)
  • Payment or webhook callback in sandbox mode if eCommerce changed
  • Queue job dispatch—confirm worker log shows processing
  • Scheduled task—check schedule:list and last run timestamp
  • Verify storage/logs/laravel.log shows no new ERROR entries

For payment-heavy apps, validate gateway callbacks after deploy. I've traced failed Khalti and eSewa callbacks to stale route cache more than once. Use the JSON formatter tool to inspect webhook payloads during testing.

Monitoring and safe debugging

Keep APP_DEBUG=false in production always. Use structured logging, external error tracking, and optionally Telescope in restricted environments. Read how to debug Laravel in production safely before enabling any debug UI on live data.

Watch CPU, memory, queue depth, and slow query logs for thirty minutes after release. Performance regressions often appear under real traffic, not during manual clicks.

Security hardening reminders

Confirm HTTPS redirects, HSTS, and that storage and .env are not web-accessible. Review the API security complete checklist when deploy touches Sanctum tokens or OAuth scopes. Rotate keys if credentials leaked during staging tests.

After major releases, run a quick speed optimization pass if response times shifted. Config caching helps, but N+1 queries exposed by new features will not fix themselves.

Post-Deploy VerificationFunctional smoke testsAuth, forms, API, paymentsCritical user journeys onlyRun within 10 minutesInfrastructure checksQueue workers aliveCron + scheduler firingRedis / DB connections OKLog review — zero new ERROR linesCompare against pre-deploy baseline in storage/logsRollback trigger: failed smoke test or error spike
Post-deploy phase of the Laravel production deployment checklist: smoke tests, infra health, log review, and rollback criteria.

Hosting, DNS, and SSL

First deploy to a new domain needs correct A/AAAA records and a valid TLS certificate. Let's Encrypt via Certbot on Ubuntu remains my default. For domain and hosting setup on Nepal-based projects, see domain registration and hosting services.

Legal-tech portals such as Court Marriage In Nepal depend on reliable uptime during business hours. A documented checklist helps non-developer staff know when a release is safe to announce.

Key Takeaways

  • Run the same ordered steps every release—pre-flight, deploy, verify—so nothing depends on memory.
  • Keep .env and storage/ in a shared directory; never commit secrets or point the vhost at the repo root.
  • Build frontend assets in CI when the server lacks Node.js 26 LTS; ship public/build with the release.
  • After symlink swap, reload PHP-FPM and run queue:restart or workers serve stale code.
  • Take a database backup before migrations; keep three to five releases for instant rollback via Deployer.
  • Smoke-test auth, writes, queues, and webhooks within ten minutes while rollback is still cheap.

People Also Ask

Should I run php artisan config:cache in production?

Yes. Config, route, and view caching cut bootstrap time on every request. Run them after migrations and before or immediately after the symlink swap. If you change .env, rebuild config cache or the app reads stale values. Never run config:cache locally when you need dynamic config during development.

How do I deploy Laravel without downtime?

Use symlink-based releases with Deployer or a similar tool. Build the new release while the old one serves traffic. Run migrations, cache configs, then atomically point the current symlink. Reload PHP-FPM to flush opcache. Users never see a half-updated tree mid-copy.

What PHP version should Laravel production use in 2026?

Laravel 13 requires PHP 8.3 or higher. Laravel 12 supports PHP 8.2+. PHP 8.5 is the current stable line; 8.4 remains widely deployed. Match the same minor version across web nodes and workers. Mixed versions cause subtle serialization and opcache bugs.

Do I need Redis for Laravel production?

Not on day one. File or database drivers work for low-traffic sites. Redis 8.x becomes worth it when you run queue workers, session scaling across nodes, or cache-heavy pages. Most production checklists should at least provision Redis before traffic spikes—especially on eCommerce builds.

Ship Laravel Releases You Can Trust

A written Laravel production deployment checklist turns release anxiety into a ten-minute routine. Start with environment parity and backups. Deploy through symlink releases. Restart workers. Verify logs and critical paths. Keep the checklist in your repo README or runbook so the next developer—or you, six months later—ships with the same discipline.

Need help hardening a live app, setting up Deployer, or migrating from manual FTP deploys? Review the portfolio of production Laravel projects, explore ongoing support and maintenance, or contact us to walk through your stack before the next release.

Frequently Asked Questions

A Laravel production deployment checklist is a repeatable ordered sequence you run on every release: pre-flight environment and version checks, dependency install, frontend asset build, migrations, config and route cache rebuild, atomic symlink swap, PHP-FPM reload, queue and scheduler restart, and post-deploy smoke tests. It turns release day from guesswork into a short routine that prevents downtime, stale opcache, missing .env keys, and workers still serving old code after a merge.

Before any release reaches the live server, confirm production .env lives outside Git in a shared directory with APP_ENV=production, APP_DEBUG=false, a set APP_KEY, verified database and Redis credentials, APP_URL matching your HTTPS domain, and queue or cache drivers pointing at production services. Match PHP and framework versions on the deploy runner and every app server with php -v and composer --version. Read every pending migration; take a backup before destructive changes. Run composer install --no-dev --optimize-autoloader in CI and commit composer.lock so production installs the exact same dependencies.

Most Laravel sites run on Ubuntu 22 or 24 with Apache or Nginx fronting PHP-FPM 8.3 or 8.4. Point the web root at current/public, never the repo root, because pointing at the project root can expose .env if misconfigured. Configure PHP-FPM with opcache enabled and opcache.validate_timestamps=0 in production. Set up Supervisor for queue:work processes, a cron entry running php artisan schedule:run every minute via the current symlink path, and Redis 8.x or Memcached 1.6.x when traffic grows. Fix ownership so deploy owns code and www-data can write to shared/storage and bootstrap/cache.

In CI, run composer install --no-dev --optimize-autoloader, npm ci, npm run build, and php artisan test. On the server, clone or rsync into releases/TIMESTAMP, link shared storage and .env, run composer install --no-dev, php artisan migrate --force, then config:cache, route:cache, and view:cache. Atomically swap the current symlink, reload PHP-FPM to flush opcache, run php artisan queue:restart, and prune old releases keeping three to five. Order matters: running config:cache before migrations or forgetting worker restarts leaves production in a split-brain state where web and background processes disagree.

Yes. Config, route, and view caching reduce bootstrap time on every request. Run them after migrations and around the symlink swap. Rebuild config cache after any .env change or the app reads stale values.

Use symlink-based releases with Deployer. Build the new release while the old one serves traffic, migrate and cache configs, atomically swap current, then reload PHP-FPM to flush opcache.

Laravel 13 requires PHP 8.3 or higher. Laravel 12 supports PHP 8.2+. PHP 8.5 is current; 8.4 remains widely deployed. Match the same minor version on every node.

Not on day one. File or database drivers work fine for low-traffic sites. Redis 8.x becomes worthwhile when you run queue workers, need sessions across multiple web nodes, or serve cache-heavy pages. Most production checklists should provision Redis before traffic spikes, especially on eCommerce builds where queue depth and session consistency matter under load. You can start simpler and add Redis when Supervisor workers or horizontal scaling enter the picture, rather than treating it as mandatory infrastructure on launch day.

Never commit .env to Git. When using Deployer zero-downtime releases, store it in a shared directory outside rotating release folders, for example /var/www/example.com/shared/.env. Each new release reads the same file so credentials persist across deploys. Confirm APP_KEY is identical across web nodes in multi-server setups. Verify database, Redis, mail, and payment credentials against staging before release. After changing any value in .env, rebuild the config cache or the application may read stale configuration from the cached bootstrap file generated at deploy time.

Deploying code does not restart long-lived queue workers; they keep serving old bootstrap code from memory until restarted. After every deploy, run php artisan queue:restart so Supervisor respawns workers gracefully. Configure Supervisor with queue:work redis pointing at the current symlink path. Confirm cron uses current, not a hard-coded old release folder, for php artisan schedule:run every minute. If you run Laravel Horizon for Redis queues, restart it during deploy the same way as standard workers. Skipping worker restart is a common cause of fixes appearing live while emails or background jobs still fail.

After the symlink swap, verify HTTP 200 on the homepage and one authenticated route, test login, password reset, and one write action, dispatch a queue job and confirm the worker log shows processing, and check storage/logs/laravel.log for new ERROR entries. For payment-heavy apps, validate gateway callbacks in sandbox mode because stale route cache has broken Khalti and eSewa webhooks in production. Watch CPU, memory, queue depth, and slow query logs for thirty minutes. Confirm HTTPS redirects, HSTS, and that storage and .env are not web-accessible. Keep APP_DEBUG=false always.

Usually not. Many production servers have no Node.js installed. Build Vite 8.x assets in CI with npm ci and npm run build, then ship the public/build directory with the release. Use Node.js 26 LTS on the build runner. Committing built assets is boring and effective for small teams running GitLab CI pipelines that lint, test, build, and deploy. This avoids installing Node on every VPS and keeps production hosts focused on PHP-FPM, the web server, workers, and Redis or Memcached rather than frontend tooling.

PHP-FPM runs as www-data on Ubuntu. Application code can belong to the deploy user, but writable paths must belong to the web user or deploy:www-data group with ug+rwx on storage and bootstrap/cache. After every deploy, confirm new release folders inherit correct ACLs. Fresh release directories lacking group write on storage/app have caused silent upload failures on real projects. The shared storage directory in the Deployer layout persists across releases, but bootstrap/cache permissions must remain writable for config and route caching during each deploy sequence.

Manual FTP or rsync suits legacy fixes only and typically causes minutes of downtime. Git pull directly on the server works for internal tools and prototypes with seconds to minutes of downtime and low complexity. Deployer symlink releases fit VPS and single or multi PHP-node setups with near-zero downtime and medium complexity; several law-firm portals share this Deployer 7 plus GitLab CI pattern. Docker or Kubernetes suits scaled API platforms with rolling updates but higher complexity. For most SMB clients in Nepal, a well-configured VPS with Deployer is often the right budget-conscious choice before containers.

Keep three to five releases when using Deployer. The deploy recipe typically sets keep_releases to 5. Pruning older folders saves disk space while leaving enough recent builds to roll back quickly via dep rollback if smoke tests fail. This pairs with taking a database backup before migrations so you can revert both code and schema when a destructive migration slips through pre-flight review. Instant symlink rollback only works while the previous release folder still exists on disk and remains compatible with the current database state.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: