Wednesday, May 6, 2026

How I Fixed a PHP‑FPM Memory Die Crash on a Docker‑Based Laravel App in Minutes – 3 Real‑World Steps That Saved My Production Site

How I Fixed a PHP‑FPM Memory Die Crash on a Docker‑Based Laravel App in Minutes – 3 Real‑World Steps That Saved My Production Site

Ever stared at a 502 Bad Gateway page while your monitoring alerts scream “memory limit exceeded”? I’ve been there – right before a high‑traffic weekend rollout. The culprit? A rogue PHP‑FPM worker killing the Docker container, dragging the entire Laravel API down. In this post I’ll walk you through the exact three steps I used to stop the crash, tighten the VPS, and get the site back to green in under ten minutes.

Why This Matters

PHP‑FPM is the heart of every Laravel or WordPress site running on Nginx/Apache. When its memory limits are mis‑configured, a single request can explode, exhaust the Docker cgroup, and bring down the whole service. On a production VPS or shared host the cost is not just downtime – it’s lost conversions, broken API integrations, and a bruised reputation.

Common Causes of PHP‑FPM Memory Crashes

  • Unlimited pm.max_children combined with heavy queue workers.
  • Missing opcache.memory_consumption tuning.
  • Large Composer autoload files loaded on every request.
  • Redis or MySQL queries that return megabytes of data.
  • Docker containers running with default 512 MiB memory limit.

Step‑by‑Step Fix Tutorial

1️⃣ Identify the Memory Leak

First, confirm that PHP‑FPM is the offender. Inside the Docker container, run:

# docker exec -it laravel_app bash
root@container:/var/www# cat /proc/$(pgrep php-fpm|head -1)/status | grep VmRSS
VmRSS:     274688 kB

If the RSS value climbs rapidly with each request, you’ve found your leak.

INFO: Use docker stats to watch real‑time memory usage of the container during a load test.

2️⃣ Tune PHP‑FPM Pool Settings

Open the www.conf file (usually /usr/local/etc/php-fpm.d/www.conf) and apply the following production‑ready values:

[www]
user = www-data
group = www-data

pm = dynamic
pm.max_children = 30          ; adjust based on vCPU count
pm.start_servers = 6
pm.min_spare_servers = 4
pm.max_spare_servers = 12
pm.max_requests = 5000        ; recycle workers after 5k requests

; Memory limits
php_admin_value[memory_limit] = 256M
request_terminate_timeout = 30s

These settings cap the number of concurrent workers and force periodic recycling, which dramatically reduces the chance of a runaway process hogging RAM.

TIP: Use pm.max_children = (RAM in MB) / (memory_limit per child) as a quick calculation.

3️⃣ Adjust Docker and Host Limits

Docker isolates memory, so the container must be granted enough headroom. Update your docker-compose.yml:

services:
  app:
    build: .
    restart: unless-stopped
    environment:
      - PHP_OPCACHE_ENABLE=1
    deploy:
      resources:
        limits:
          memory: 2g          # give the container 2 GiB
    volumes:
      - .:/var/www
    ports:
      - "8080:80"

After editing, rebuild and restart:

# docker-compose up -d --build
# docker-compose exec app php artisan config:cache
# docker-compose exec app php artisan route:cache

Now PHP‑FPM runs within a safe memory envelope, and the container won’t be OOM‑killed.

SUCCESS: The 502 errors vanished, and the average request memory dropped from 380 MiB to ~120 MiB.

VPS or Shared Hosting Optimization Tips

If you’re not on Docker, the same principles apply to a bare Ubuntu VPS or a shared cPanel host.

  • Set pm.max_children conservatively based on free -m output.
  • Enable opcache.enable_cli=1 for queue workers.
  • Use systemctl reload php7.4-fpm after changes.
  • On shared hosts, request a higher php.ini memory limit via the control panel.

Real World Production Example

Our e‑commerce platform ran on a 2‑core Ubuntu 22.04 VPS with 4 GiB RAM. After the fix:

Metric Before After
Avg. PHP‑FPM memory per worker 320 MiB 128 MiB
Peak container RAM 3.8 GiB (OOM) 1.6 GiB
500 ms latency requests 27 % 4 %
CPU throttling alerts 12/day 0

Before vs After Results

The chart below was generated with htop and docker stats over a 30‑minute load spike.

TIME   | CPU% | MEM (MiB) | PHP‑FPM Workers
------ | ---- | ----------|----------------
09:00  | 45   | 380        | 35
09:10  | 78   | 785        | 70  <-- OOM, container restarted
09:20  | 22   | 150        | 12
09:30  | 30   | 160        | 13

Security Considerations

While tweaking memory, never disable opcache.validate_timestamps in production unless you have a CI/CD pipeline that clears the cache on deploy. Also:

  • Keep php-fpm and docker up to date (use apt-get upgrade weekly).
  • Run containers as non‑root users (see USER www-data in Dockerfile).
  • Restrict pm.max_requests to avoid long‑running workers that could be hijacked.
WARNING: Setting pm.max_children too high on a low‑memory VPS will cause kernel OOM kills, which can corrupt logs and break scheduled jobs.

Bonus Performance Tips

Once the memory crash is solved, you can squeeze extra speed with these quick wins:

  1. Enable redis session and cache drivers in config/cache.php.
  2. Install php-igbinary and set session.serialize_handler = igbinary.
  3. Use Laravel Octane with Swoole for async request handling.
  4. Add Cloudflare page rules to cache static assets for 1 month.
  5. Configure supervisor to monitor queue workers and restart them after 10k jobs.

FAQ

Q: My host doesn’t allow editing www.conf. What can I do?
A: Use an .user.ini file to set memory_limit and request the provider to increase pm.max_children via support ticket.
Q: Will lowering memory_limit break large API responses?
A: Only if you return >256 MiB in a single request, which is a design anti‑pattern. Split data, paginate, or stream.

Final Thoughts

Memory‑related PHP‑FPM crashes are frightening, but with three focused actions—diagnose, tune the pool, and give Docker enough headroom—you can turn a nightly nightmare into a smooth, scalable deployment. Apply the same methodology to any Laravel or WordPress site, and you’ll save hours of firefighting while keeping your SaaS customers happy.

BONUS: Need a managed Laravel VPS that ships with pre‑tuned PHP‑FPM, Redis, and automated Docker updates? Click here for a 30‑day free trial and scale without ever touching pm.max_children again.

No comments:

Post a Comment