Lập trình · 22/09/2026

Laravel 13 Course – Lesson 34: Deploying Laravel with Nginx, Queue Workers and a Scheduler

Lesson 34 — Deploy Laravel with Nginx, workers and a scheduler: A successful web response does not prove the entire application operates correctly. TaskFlow also needs queue consumers, scheduled execution, durable private files and refreshed long-lived processes. This lesson provides templates and acceptance criteria, not a claim of a completed production deployment.

Học Laravel 13 – Bài 34: Deploy Laravel với Nginx, queue worker và scheduler

Lesson 34 — Deploy Laravel with Nginx, workers and a scheduler: A successful web response does not prove the entire application operates correctly. TaskFlow also needs queue consumers, scheduled execution, durable private files and refreshed long-lived processes. This lesson provides templates and acceptance criteria, not a claim of a completed production deployment.

1. Identify the processes

ComponentPurposeAcceptance check
Nginx and PHP-FPMHTTP/TLS and Laravel requestsLogin, policies, API, uploads/downloads
Database queue workersMaintenance, notifications, broadcastsCorrect queues drain without unexplained failures
Scheduler timerInvoke schedule:run each minuteSuccessful service and actual due task completion
Optional ReverbRealtime connectionsAuthorized private events through WSS

Templates in taskflow/deploy target Linux with systemd, Nginx and PHP 8.4 CLI/FPM. Match extensions across runtimes; worker timeout enforcement requires supported PCNTL behavior. The Windows development environment does not validate the Linux service stack.

2. Separate releases from durable data

Use /srv/taskflow/releases/{release-id} for code, current for the active release link and shared for .env, storage and an intentionally selected SQLite database. SQLite needs an absolute path outside disposable releases. MySQL/PostgreSQL require their own connection and engine tests. Release cleanup must not delete durable data.

Provision the taskflow user and narrowly scoped FPM/worker permissions. Nginx reads public assets, not secrets. Only required storage/cache locations need application writes; never chmod the entire project 777. Private PDFs remain outside public and use authorized controller downloads. Keep APP_KEY stable rather than regenerating it per deployment.

3. Serve only the public directory

This excerpt belongs inside a provisioned HTTPS server block. nginx.conf.example contains the full skeleton, HTTP redirect and optional Reverb vhost. Replace hostnames, FPM socket and certificate paths for the actual host; placeholders are not a ready-to-run certificate configuration.

# Inside the HTTPS application server block, after provisioning TLS:
root /srv/taskflow/current/public;
index index.php;
client_max_body_size 3m;
location / {
    try_files $uri $uri/ /index.php?$query_string;
}
location = /index.php {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $realpath_root/index.php;
    fastcgi_param DOCUMENT_ROOT $realpath_root;
    fastcgi_pass unix:/run/php/php8.4-fpm.sock;
    fastcgi_hide_header X-Powered-By;
}
location ~ \.php$ { return 404; }
location ~ /\.(?!well-known).* { deny all; }

Only index.php reaches FPM; other PHP paths are rejected. $realpath_root resolves the release symlink. The 3 MB request limit allows overhead around the 2 MB PDF rule, but PHP upload/post limits must agree. Run nginx -t before reloading and test real uploads; configuration review does not prove TLS or filesystem permissions.

4. Separate workers by queue

# /etc/systemd/system/taskflow-worker@.service
[Unit]
Description=TaskFlow queue worker (%i)
After=network.target
[Service]
Type=simple
User=taskflow
Group=taskflow
WorkingDirectory=/srv/taskflow/current
ExecStart=/usr/bin/php8.4 artisan queue:work database --queue=%i --sleep=3 --tries=3 --timeout=60 --max-time=3600
Restart=always
RestartSec=5
KillSignal=SIGTERM
TimeoutStopSec=90
UMask=0027
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target

%i is the instance name: maintenance, notifications, broadcasts or default. Separate consumers reduce starvation between queues, but still share host capacity. Tune process counts from measurements.

The 60-second worker timeout is below the database queue's 90-second retry_after. Existing job-specific 15/20-second limits are lower. TimeoutStopSec allows shutdown time; killed jobs can still be retried and need retry-safe behavior. max-time periodically recycles workers; Restart=always handles normal exits too, without undoing an explicit systemd stop.

After host review, validate units with systemd-analyze verify, daemon-reload and start the intended instances. Never restart all PHP processes on a shared machine. Inspect exact-unit journals and failed jobs before retrying anything broadly.

5. Minute timer, timezone-aware application schedule

# taskflow-scheduler.service
[Unit]
Description=Run TaskFlow due scheduled commands
After=network.target
[Service]
Type=oneshot
User=taskflow
Group=taskflow
WorkingDirectory=/srv/taskflow/current
ExecStart=/usr/bin/php8.4 artisan schedule:run
TimeoutStartSec=120
UMask=0027
NoNewPrivileges=true
PrivateTmp=true

# Separate file: taskflow-scheduler.timer
[Unit]
Description=Check TaskFlow schedule every minute
[Timer]
OnCalendar=*-*-* *:*:00
AccuracySec=1s
Unit=taskflow-scheduler.service
[Install]
WantedBy=timers.target

Use two separate files. The timer does not start an overlapping instance while its service remains active. A service exceeding 120 seconds fails and needs investigation; this is not proof every task completes within two minutes. Do not run an additional cron or schedule:work for the same schedule.

TaskFlow schedules overdue reporting at08:00 and expired-token pruning at02:00 Asia/Ho_Chi_Minh; local schedule:list displays corresponding UTC01:00/19:00. Shared cache configuration is required for distributed locks. Missed application tasks are not automatically replayed after downtime. Pruning deletes expired token records, so review retention before enabling it.

6. Reverb and production environment

Optional realtime uses loopback Reverb on127.0.0.1:8080 and a separate WSS hostname. Proxy Upgrade/Connection headers and both /app WebSockets and /apps API traffic. External REVERB_HOST/PORT/SCHEME differs from the server bind address. Only public app keys belong in Vite assets. Explicit allowed origins do not replace private-channel authorization.

Use production environment, debug disabled, HTTPS URLs, secure cookies and shared persistence where required. Array cache cannot coordinate processes. Configure a real mail transport if delivery is expected; the log mailer is not inbox delivery.

7. A release sequence with stop conditions

  1. Verify commit/CI, backups and a tested restore plan.
  2. Build from lockfiles; test with development dependencies, then create a no-dev PHP artifact with correctly configured Vite assets.
  3. Attach shared data/environment, verify permissions and database target.
  4. Review schema compatibility; migrate only within the approved operational plan, never seed demo users.
  5. Build config, route, view and event caches in the new release; stop on errors.
  6. Switch current deliberately, reload the appropriate FPM pool and restart the named workers/Reverb.
  7. Verify health, login, policies, task writes, private files, queue draining, scheduled completion and optional realtime.

Do not indiscriminately optimize:clear: it also clears default application-cache keys. Record release, commit, time, migrations and smoke results. Default /up is not evidence that database, queues, SMTP and backups all work.

8. Rollback is more than a symlink switch

Previous code must remain compatible with current schema/data. Do not automatically migrate:rollback after deployment failure; reverse migrations can lose data or break running processes. Prefer expand/contract changes and account for queued payload compatibility. Restoring data requires agreed RPO/RTO and consideration of writes after the backup.

Templates exist and schedule definitions were checked locally. Linux Nginx/FPM/systemd, certificates, permissions, restarts and rollback have not been exercised at this checkpoint. Rehearse on a dedicated staging host and retain evidence before using production. The next lesson adds health checks and backup planning.

References: Laravel deployment, Nginx WebSocket proxy, systemd timer reference. Navigation: Lesson 33 · Roadmap.

Laravel 13 course navigation

Previous lesson (33) · Next lesson (35) · All 37 lessons

Discussion

Comments 0

Sign in to comment

You need an account to join the discussion and reply to other readers.

Sign inRegister

No comments yet. Be the first to share your thoughts.