The page was blank. Sometimes it was a 502. Staging was fine. Production had been fine yesterday.
I opened Laravel’s log. Nothing. I added a Log::info at the top of public/index.php. Still nothing. That is when you are no longer debugging PHP.
The split that matters
A request to a typical Laravel production box is at least three stories:
- Nginx accepted a socket
- Nginx talked to PHP-FPM over a unix socket or TCP
- PHP actually ran
index.php
If (3) never happens, your application log is a diary of a process that was not invited.
Symptoms that are lying to you
- Blank 200. Sometimes Nginx, sometimes PHP dying before it can emit a body, sometimes a middleware that returns empty. Check response headers before you check Blade.
- 502 Bad Gateway. Nginx did not get a valid response from upstream. This is not
abort(502)in Laravel unless you wrote that, and you probably did not. - 504. Upstream too slow, or FPM pool exhausted, or a lock.
- Works on the second refresh. Pool, restart, or a worker that is wedged.
The failed attempts
I have, more than once:
- deployed again
- blamed OPcache
- increased
pm.max_childrenwithout measuring - stared at a Laravel exception handler that never ran
The useful checks are dull:
sudo tail -n 100 /var/log/nginx/error.log
sudo tail -n 100 /var/log/php8.3-fpm.logLook for connect() to unix:/run/php/php8.3-fpm.sock failed, upstream sent too big header, server reached pm.max_children, or a segfault.
Permissions on the socket are a classic. Nginx user cannot talk to the FPM socket, so you get 502, and the application is innocent.
The runbook version lives at Problem Log: Nginx/PHP-FPM.
What I learned
When the application is silent, believe the silence. Debug the boundary: Nginx error log, FPM error log, socket, pool. Add application logging only after you can prove PHP ran.
This is not glamorous. It is how you stop spending an afternoon in the wrong process.