Thursday, May 7, 2026

Laravel Queue Workers Crashing After Docker Deploy: 5 Quick Fixes for 500 Errors on Nginx in a Production VPS Environment

Laravel Queue Workers Crashing After Docker Deploy: 5 Quick Fixes for 500 Errors on Nginx in a Production VPS Environment

You’ve just pushed a fresh Docker image to your Ubuntu‑based VPS, hit docker compose up -d, and moments later the Nginx logs start spitting out 500 errors. Your Laravel queue workers die instantly, and the whole API grinds to a halt. It’s the kind of nightmare that makes even seasoned PHP devs want to pull their hair out.

What you’ll get: Five battle‑tested fixes, a production‑ready configuration checklist, and a real‑world before‑and‑after case study that turns those dreaded 500s into green‑light traffic.

Why This Matters

Queue workers are the backbone of any Laravel SaaS that processes emails, notifications, or API calls asynchronously. When they crash, users experience delays, revenue pipelines stall, and support tickets pile up. In a VPS environment where resources are finite, a mis‑configured Docker or Nginx layer can waste CPU cycles, flood the error log, and mask deeper PHP‑FPM or Redis issues.

Common Causes of 500 Errors After Docker Deploy

  • Incorrect php-fpm pool settings inside the container.
  • Missing .env variables leading to unhandled exceptions.
  • Supervisor not restarting queue workers after a container restart.
  • Redis connection timeout due to Docker network isolation.
  • File permission mismatches between host and container volumes.

Step‑By‑Step Fix Tutorial

1️⃣ Align PHP‑FPM Settings with VPS Limits

Open the Dockerfile or your custom php-fpm.conf and ensure the pool matches the VPS CPU/RAM. Example for a 2‑core, 4 GB VPS:

[www]
user = www-data
group = www-data
listen = /run/php/php7.4-fpm.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 30
pm.start_servers = 6
pm.min_spare_servers = 3
pm.max_spare_servers = 12
php_admin_value[error_log] = /var/log/php-fpm.log
php_admin_flag[log_errors] = on

After editing, rebuild the image and restart:

docker compose build php
docker compose up -d php

2️⃣ Pass All .env Variables to Docker

Make sure your docker-compose.yml references a host‑mounted .env file or uses environment: blocks for each key.
services:
  app:
    image: yourcompany/laravel-app:latest
    env_file:
      - ./.env
    volumes:
      - ./:/var/www/html
    depends_on:
      - redis
      - db

3️⃣ Configure Supervisor Inside the Container

Supervisor keeps queue workers alive across container restarts. Add a supervisord.conf like this:

[supervisord]
nodaemon=true

[program:laravel-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work redis --sleep=3 --tries=3
autostart=true
autorestart=true
user=www-data
numprocs=3
redirect_stderr=true
stdout_logfile=/var/log/queue.log

Don’t forget to install Supervisor in your Dockerfile:

RUN apt-get update && apt-get install -y supervisor
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

4️⃣ Fix Redis Network Bridge

If your container can’t resolve redis, queue workers will throw a 500 error.

Use Docker’s internal DNS by setting an explicit network and alias:

networks:
  laravel:
    driver: bridge

services:
  redis:
    image: redis:6-alpine
    networks:
      laravel:
        aliases:
          - cache

Then update .env:

REDIS_HOST=cache
REDIS_PASSWORD=null
REDIS_PORT=6379

5️⃣ Harden File Permissions

Docker can mount files with the wrong uid/gid, causing Laravel to fail when writing logs or cache. Run this after container start:

docker exec -it $(docker ps -qf "name=app") bash -c "
chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache;
chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache"
After applying the five fixes, you should see queue workers staying alive and Nginx returning 200 OK for every request.

VPS or Shared Hosting Optimization Tips

  • Swap Management: Disable swap on production VPS to avoid latency spikes ( sudo swapoff -a ).
  • OPcache: Enable opcache.enable=1 and set opcache.memory_consumption=256 for fast PHP execution.
  • MySQL Tuning: Use innodb_buffer_pool_size=70% RAM and max_connections=500 for high‑throughput APIs.
  • Cloudflare Caching: Cache static assets at edge, but bypass /api/* routes.
  • System Monitoring: Set up htop, netdata or New Relic to catch resource saturation early.

Real World Production Example

Acme SaaS runs a 8‑core, 16 GB VPS on Ubuntu 22.04. After a rushed Docker rollout, the queue:work processes crashed, generating 1,200 500 errors per minute. Applying the five steps above reduced error rate to zero within 10 minutes. CPU usage dropped from 95% to 42% because PHP‑FPM pools were finally respecting pm.max_children.

Before vs After Results

Metric Before After
500 Errors/min 1,200 0
CPU Utilization 95% 42%
Queue Lag 45s 3s

Security Considerations

  • Never expose APP_DEBUG=true in production – it leaks stack traces.
  • Use docker secrets or Vault for database passwords instead of plain .env files.
  • Limit Supervisor to run as www-data, not root.
  • Apply Nginx limit_req and limit_conn to protect against DDoS spikes.
  • Enable HTTP Strict Transport Security (HSTS) and TLS 1.3 in your Nginx block.

Bonus Performance Tips

  1. Cache Heavy Queries: Use Redis Cache::remember() with a 10‑minute TTL.
  2. Queue Prioritization: Separate high‑priority jobs into a dedicated high queue with more workers.
  3. Lazy Loading Avoidance: Eager load relationships (with()) for API endpoints that hit the DB heavily.
  4. Static Asset CDN: Offload /public/css and /js to Cloudflare or Amazon S3.
  5. Composer Optimize: Deploy with composer install --no-dev --optimize-autoloader.

FAQ

Q: My queue still restarts after a Docker image update. What gives?

A: Ensure Supervisor is running as PID 1 in the container or use Docker’s --restart unless-stopped flag combined with docker compose up -d to keep the service alive.

Q: Do I need a separate Redis container for each Laravel app?

No. A single Redis instance can host multiple databases; just set REDIS_DB per app.

Q: How can I monitor queue health without a paid SaaS?

Use php artisan queue:failed and a simple cron that pings /health. Pair it with netdata for real‑time graphs.

Final Thoughts

Docker is a fantastic tool, but when you combine it with a tightly‑packed VPS, missing a single environment variable or mis‑sizing PHP‑FPM can cascade into massive 500 storms. The five fixes above give you a repeatable, version‑controlled checklist that keeps Laravel queue workers alive, Nginx happy, and your users paying.

If you’re looking for a low‑cost, high‑performance hosting partner that already ships with optimized PHP‑FPM, Redis, and MySQL on a managed VPS, check out cheap secure hosting. Their one‑click Docker installer and built‑in Cloudflare CDN can shave milliseconds off every request.

Tip: Keep this article bookmarked. When your next Docker deploy triggers a 500, run through the checklist – you’ll be back online in under ten minutes.

No comments:

Post a Comment