[ GUIDE ]

How to debug Laravel timeouts

Where each layer fails (PHP, PHP-FPM, Nginx, database), how to tell them apart, and how to fix without just raising every timeout.

QUICK ANSWER

How do I debug a Laravel 504 timeout?

Identify the layer that gave up first. Check Nginx access logs for the 504 — you'll see URL, timestamp, and upstream_response_time. If upstream_response_time is near fastcgi_read_timeout, Nginx dropped. If near request_terminate_timeout, PHP-FPM killed the worker. If it's max_execution_time, PHP timed itself out. The layer tells you where to look — most 504s are ultimately a slow database query or external HTTP call, visible in an APM's per-request trace.

Updated · 2026-04-13

The five timeout layers

A Laravel HTTP request touches multiple layers. Each has its own timeout:

Layer Default Config
Client (browser, curl)Varies (~30s)Client-specific
Load balancer (ALB, CF)60s AWS ALB defaultidle_timeout
Nginx upstream60sfastcgi_read_timeout
PHP-FPM request0 (unlimited)request_terminate_timeout
PHP script30smax_execution_time
Database statementUnlimited (dangerous)statement_timeout (Postgres)

Diagnose the layer that failed

Nginx access log

bash
# Enable timing fields in log_format
log_format upstream_timing '$remote_addr $request $status '
                           'request=$request_time upstream=$upstream_response_time';

# Look for 504s with timing
grep ' 504 ' /var/log/nginx/access.log | tail

If upstream_response_time is ~equal to fastcgi_read_timeout, Nginx gave up. If upstream_response_time is -, PHP-FPM didn't respond at all (crashed, full worker pool, or no workers running).

PHP-FPM slowlog

/etc/php/8.2/fpm/pool.d/www.conf

ini
slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 10s
request_terminate_timeout = 60s

PHP-FPM dumps the stack trace of any request taking longer than request_slowlog_timeout. Priceless for catching where the hang actually happens — often inside a database driver or file operation.

Database-level timeouts

config/database.php (PostgreSQL)

php
'pgsql' => [
    'driver' => 'pgsql',
    // ...
    'options' => [
        PDO::ATTR_TIMEOUT => 5,
    ],
    // Per-session statement timeout
    'statement_timeout' => '15s',
],

Without statement_timeout, a query can hold a PHP-FPM worker indefinitely. Set a value lower than your request timeout so DB hangs fail recoverably instead of taking out the worker.

Common 504 causes

  1. Synchronous external HTTP call with no timeout. Default Guzzle = unlimited. Set Http::timeout(5) on every external call.
  2. Slow DB query on unindexed column. EXPLAIN ANALYZE reveals full-table scans.
  3. Exhausted PHP-FPM worker pool. pm.max_children too low, legitimate requests queue, queue exceeds timeout.
  4. Lock contention on a hot row. SELECT ... FOR UPDATE blocking other requests. Visible in Postgres's pg_locks.
  5. Blocking cron job on the same server. A scheduled task hogging CPU or disk starves web workers.

Don't just raise every timeout

The instinct to bump fastcgi_read_timeout to 300s papers over the real issue. A request taking 300s shouldn't run in a request at all — queue it. Use a synchronous request for the queue dispatch and return a 202 with a polling URL or a webhook subscription.

THE EASY WAY

NightOwl surfaces slow and timed-out requests with trace drilldown

Every request is recorded with duration, status, and full per-span breakdown. Filter by 504 or slow duration to find timeouts; click a request to see the dominant span — usually the DB query or external HTTP call that hung.

bash
composer require nightowl/agent
php artisan nightowl:install

From $5/month flat. Data in your PostgreSQL.

Frequently asked questions

Why am I getting 504 Gateway Timeout errors in Laravel?

Something in the request took longer than your web server's upstream timeout. Common culprits: a hung database query, a slow external API called synchronously, a PHP-FPM worker exhausted and queuing requests, or max_execution_time hit. The 504 is the symptom — the trace of the affected request shows which span actually took the time.

What's the default PHP timeout in Laravel?

max_execution_time defaults to 30 seconds for HTTP requests in most php.ini configurations. CLI is unlimited by default. Laravel doesn't override this unless you do it explicitly with set_time_limit() or ini_set(). Nginx and PHP-FPM each have their own timeouts — request_terminate_timeout in PHP-FPM, fastcgi_read_timeout in Nginx — which must all be compatible.

How do I increase the Laravel request timeout?

Three layers, all must increase together. PHP: set_time_limit(120) or ini_set('max_execution_time', 120). PHP-FPM: request_terminate_timeout = 120. Nginx: fastcgi_read_timeout 120s. If any one is lower, requests still timeout at that layer. For most apps, a better answer is to not run long work in a request — use queues.

What's the difference between 504 and 502 in Laravel?

504 Gateway Timeout — upstream (PHP-FPM) didn't respond within the timeout. The request was in progress but too slow. 502 Bad Gateway — upstream crashed or closed the connection. Usually a PHP fatal error, worker crash, or PHP-FPM running out of worker slots. Both surface from Nginx; their causes differ.

How do I find which request timed out in Laravel?

Check Nginx's access log for 504 status codes — the URL and timestamp give you the request. Then check PHP-FPM's slowlog (configure with request_slowlog_timeout) to see the PHP stack trace at the moment of the timeout. Or use an APM that records every request with duration and status — slow + 504 requests surface directly.

Why do Laravel queue jobs time out?

Two different timeouts apply. Laravel's per-job timeout (timeout property on the job class, default 60s) — the worker kills the child process after this. PHP's max_execution_time on the worker process itself. If your job exceeds Laravel's timeout, it's killed and marked released; if PHP-level times out first (unusual, would need to be lower), the job fails with a fatal error.

How do I prevent database queries from hanging Laravel requests?

Set statement_timeout in PostgreSQL or max_execution_time in MySQL on the connection. In Laravel, configure this via the connection options in config/database.php. A query that exceeds the timeout errors with a recoverable exception instead of hanging the PHP-FPM worker indefinitely.

What's the right PHP-FPM pm.max_children setting?

Roughly: (available_memory - overhead) / average_process_memory. For an 8GB server with 100MB per process and ~2GB overhead, that's (8000-2000)/100 = 60 children. Set too low and requests queue and 504. Set too high and workers swap memory and everything slows down. Monitor with pm.status_path and tune from data, not guesses.

PRICING

Flat pricing. No event caps. No per-seat fees.

14-day free trial, no credit card. Your PostgreSQL, your data.

HOBBY

$5 /month

1 app · 14 days lookback · all Laravel events

TEAM

$15 /month

Up to 3 connected apps · unlimited environments · all Laravel events

AGENCY

$69 /month

Unlimited apps · unlimited agent instances · same flat rate at any traffic

Related