Client Project Lessons

Lessons from a Late-Night Incident Response and What Changed After

By Nikhil Bhangale··2 min read

A candid recap of root cause, fixes, and the new guardrails we shipped for resilience.

Lessons from a Late-Night Incident Response and What Changed After

It was 11:47 PM. A message came in: "Site is down. Getting 502 everywhere."

This is a recap of that incident, what caused it, how we fixed it, and what we put in place so it never happens again.

The Context

A multi-tenant SaaS platform on a single Ubuntu 22.04 VPS (4 vCPU, 8GB RAM) running Nginx + PHP-FPM + PostgreSQL. About 2,000 active users across time zones.

Timeline

TimeEvent
23:47First 502 reports from users
23:49Me SSH'd in, checking nginx status
23:52Identified PHP-FPM as the culprit
00:08Service restored
00:35Root cause confirmed

Root Cause

A scheduled cron job ran a large database export at 23:30. It spawned 40+ PHP processes simultaneously, exhausting the pm.max_children limit in PHP-FPM. Nginx queued requests, then timed out. 502s everywhere.

# This showed the problem
journalctl -u php8.2-fpm --since "23:30" | grep "WARNING"
# WARNING: [pool www] server reached pm.max_children setting (10)
# WARNING: You may need to increase this value

Immediate Fix

# Bumped max_children temporarily
sed -i 's/pm.max_children = 10/pm.max_children = 25/' /etc/php/8.2/fpm/pool.d/www.conf
systemctl reload php8.2-fpm

Site came back within seconds.

Long-Term Guardrails

1. Rate-Limited the Cron Job

# Before (runs everything at once)
0 23 * * * php /var/www/app/artisan export:run

# After (batched with nice and ionice)
0 23 * * * nice -n 10 ionice -c 3 php /var/www/app/artisan export:run --batch-size=50

2. PHP-FPM Tuning

Set proper pool limits based on available RAM:

pm = dynamic
pm.max_children = 30
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10
pm.process_idle_timeout = 10s
pm.max_requests = 500

3. Added Monitoring Alert

# Grafana alert rule
condition: php_fpm_active_processes > 25
for: 2m
annotations:
  summary: "PHP-FPM near capacity — check cron jobs"

What I Learned

Never run heavy jobs at peak-adjacent hours. And always have pm.status_path enabled so you can see FPM pool usage in real time.

A good incident response is 20% fixing the problem and 80% making sure it can't happen again.

#incident-response#linux#nginx#postgresql#monitoring
← Back to all posts