DevOps

Laravel React App Stalls After 30KB: Fix Timeouts

Debug Laravel + React (InertiaJS) app stalling after ~30KB load on hosters like aeza. Fix PHP-FPM timeouts, Nginx proxy issues, output buffering, chunked transfer, and network stalls with logs and traces.

1 answer 1 view

Why does my Laravel + React (InertiaJS) app stall after sending ~30 KB (only title + CSS) and the browser retries every ~45 seconds on a specific hoster (aeza, LLC/ru)?

Observations:

  • Stack: Laravel + React + InertiaJS
  • After deployment the page begins loading, ~30 KB are received (title + CSS) and then the browser waits a long time; Wireshark shows the client re-requesting data every ~45 seconds
  • I suspected middleware/exceptions and tested deploying the default react-starter-kit — same behavior
  • If I comment out autoload/bootstrap and return static content (e.g., “hello world” + date) the page loads normally
  • The issue does not occur on another hosting provider

What are the most likely causes (webserver/reverse-proxy timeouts, PHP-FPM/worker limits, output buffering, chunked transfer / Content-Length mismatch, hosting network/firewall or ISP filtering, TCP-level retransmits)? Which headers, logs, or network traces should I inspect to pinpoint the problem, and what concrete debugging steps or configuration changes typically resolve such host-specific stalls?

Your Laravel React app stall after sending ~30 KB most often points to a host-specific reverse-proxy or PHP-FPM timeout/buffering issue (rather than your app code), or to the host interfering with chunked/streamed responses so the connection is left half-open and the browser retries. Check proxy/webserver timeouts (e.g., proxy_read_timeout / fastcgi_read_timeout), PHP-FPM request_terminate_timeout / max_execution_time, output buffering and chunked/Content-Length headers, and capture TCP traces to see resets or retransmits. The quickest way to confirm is a curl --no-buffer test, a small streaming PHP script, and server-side captures (nginx logs, php-fpm slowlog, tcpdump/Wireshark) — these will show whether the backend is killed, the proxy is timing out, or packets are being dropped.


Contents


Most likely causes (quick summary)


Webserver / reverse-proxy timeouts and buffering

Why this often matches your symptoms

  • The host’s front-end (load balancer or proxy) can return the first buffered bytes (HTML head/CSS) and then wait for the backend to finish. If the front-end applies a read timeout or buffers until the backend completes and the backend stalls, the client sees a partial page and then nothing. Different hosts have different default values (some use 30s, 45s, 60s, etc.).

What to check/configure

  • For Nginx-style proxies, verify and temporarily raise these values so they exceed any PHP-FPM timeout:
  • proxy_read_timeout, proxy_send_timeout, send_timeout
  • fastcgi_read_timeout, fastcgi_send_timeout
  • Disable proxy buffering for streaming endpoints while testing:
  • proxy_buffering off; and fastcgi_buffering off;
  • Example snippet:
nginx
location ~ .php$ {
 include fastcgi_params;
 fastcgi_pass unix:/run/php/php-fpm.sock;
 fastcgi_read_timeout 300;
 fastcgi_send_timeout 300;
 fastcgi_buffering off;
}

Useful references: Nginx configuration guide https://docs.nginx.com/nginx/admin-guide/web-server/configuration/ and chunked transfer behavior https://www.nginx.com/blog/nginx-and-http-chunked-transfer-encoding/.

Symptoms in logs

  • Nginx error.log lines like upstream prematurely closed connection while reading response or many 499 (client closed) / 504 (gateway timeout) entries indicate proxy/backend timeout interplay.

PHP-FPM / Worker Limits & Script Timeouts (Laravel timeout)

Why PHP-FPM matters

  • PHP-FPM can forcibly terminate long requests with request_terminate_timeout. If the worker is killed mid-response the connection may be left hanging or closed without a proper final chunk. max_execution_time in php.ini and set_time_limit() are also relevant.

What to inspect

  • Pool config (e.g., /etc/php/7.x/fpm/pool.d/www.conf):
  • request_terminate_timeout = 300s
  • request_slowlog_timeout = 5s
  • slowlog = /var/log/php-fpm/www-slow.log
  • pm.max_children, pm.max_requests
  • PHP settings: max_execution_time, memory_limit, output_buffering (phpinfo() or ini_get() can show values).

How a mismatch appears

  • If PHP-FPM’s request_terminate_timeout is lower than proxy read timeout, PHP may be killed while the proxy still waits (or vice versa). Align them so the proxy allows enough time for the backend to finish, and add slowlog to capture what the PHP worker was doing when killed.

Docs: https://www.php.net/manual/en/install.fpm.configuration.php and https://www.php.net/manual/en/function.set-time-limit.php.


Output buffering, chunked transfer & Content-Length mismatch

Why this causes a stall

  • If the server (or proxy) buffers output and the backend never flushes/finishes, the client sees initial bytes and then waits for the rest. If the response says Content-Length but the bytes don’t match, or Transfer-Encoding: chunked is mangled by an intermediary, the browser will sit waiting.

What to check

  • Response headers: Transfer-Encoding, Content-Length, Connection.
  • PHP settings: output_buffering, implicit_flush, use of ob_start() / ob_flush() in code.
  • Nginx buffering: proxy_buffering / fastcgi_buffering.

Quick server-side streaming test (save as stream.php):

php
<?php
ini_set('output_buffering', 'Off');
ini_set('implicit_flush', 1);
while (ob_get_level()) ob_end_flush();
header('Content-Type: text/plain');
echo str_repeat(' ', 1024); // push past buffers
echo "start\n"; flush();
for ($i=1;$i<=6;$i++) { echo "tick $i\n"; flush(); sleep(5); }
echo "done\n";
  • curl -v --no-buffer http://yourhost/stream.php should display ticks. If you only ever see the first chunk, the host/proxy is buffering or dropping further chunks.

References: MDN transfer encoding https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding and Cloudflare chunked explanation https://www.cloudflare.com/learning/performance/what-is-http-chunked-transfer-encoding/.


Network, firewall, ISP filtering and TCP retransmits

Why check the network

  • A middlebox (firewall, NAT, IDS) may silently drop or reset long-lived/inactive connections after X seconds, or it may drop packets causing retransmits and long delays. The ~45s interval you observed hints at a middlebox timeout or an OS-level retransmission/backoff policy.

What to collect

  • Packet capture (server side and from a client): tcpdump -i any host <client-ip> and port 80 -s 0 -w capture.pcap, then open in Wireshark to inspect RST/FIN, retransmissions, duplicate ACKs, and whether the final zero-size chunk is sent. See Wireshark docs https://www.wireshark.org/docs/wsug_html_retransmissions.html.
  • Look for repeated SYNs or reissued GETs in the capture — that means the connection was closed/reset and the browser retried.

Common signs

  • RST from an intermediate device or the server at ~45s.
  • Repeated retransmissions and exponential backoff before the browser issues a fresh GET.

Cloudflare/Workers/edge limits can also forcibly terminate requests — see worker limits https://developers.cloudflare.com/workers/platform/limits/#worker-timeout.


What headers, logs and network traces to inspect (practical checklist)

Headers to capture (via curl or browser devtools)

  • Transfer-Encoding, Content-Length, Connection, Keep-Alive, Server, Via, X-Accel-Buffering, X-Forwarded-For, X-Request-Id.

Useful curl commands

bash
# show headers + raw response
curl -v --no-buffer http://yourhost/ -o /dev/null

# only headers
curl -I http://yourhost/

Server logs

  • Nginx: /var/log/nginx/access.log and /var/log/nginx/error.log — look for 499, 502, 504, and upstream prematurely closed connection.
  • PHP-FPM: error log, slowlog (use request_slowlog_timeout), check for OOM kills in dmesg.
  • Laravel logs: storage/logs/laravel.log for exceptions.

Network traces

  • tcpdump on server & client during reproduction; inspect with Wireshark for retransmits, RSTs, or missing final chunk.

Process tracing

  • strace -p <php-fpm-pid> while reproducing can show whether the PHP worker is blocked on network I/O or being killed.

Concrete debugging steps and tests (ordered)

  1. Reproduce from a remote client and compare to other host:
  • curl -v --no-buffer http://problem-host/ vs curl -v --no-buffer http://good-host/.
  1. Check raw headers:
  • curl -I http://problem-host/ — note Transfer-Encoding / Content-Length.
  1. Run streaming test (stream.php above) and confirm whether multiple chunks arrive.
  2. Tail logs while reproducing:
  • tail -f /var/log/nginx/error.log /var/log/nginx/access.log /var/log/php-fpm.log /var/log/php-fpm/slow.log
  1. Capture packets:
  • sudo tcpdump -i any host <client-ip> and port 80 -s 0 -w /tmp/trace.pcap
  1. If PHP-FPM is suspected:
  • Increase request_terminate_timeout and request_slowlog_timeout, enable slowlog, and reproduce.
  1. If proxy suspected:
  • Temporarily set fastcgi_read_timeout and proxy_read_timeout to 300s and fastcgi_buffering off to see if behavior changes.
  1. If network layer suspected:
  • Ask host to run tcpdump on their edge and to confirm any firewall/NAT timeouts or DDoS rules.
  1. Interpret results:
  • If server logs show the worker was killed -> PHP-FPM timeout or OOM.
  • If capture shows RST from host edge at ~45s -> ask host about connection kill time.
  • If proxy returns a 502/504 in logs -> upstream/timeouts.

Configuration changes and quick fixes to try now

  • Align timeouts (example values for testing):
  • Nginx: fastcgi_read_timeout 300; proxy_read_timeout 300; send_timeout 300;
  • PHP-FPM: request_terminate_timeout = 300s
  • PHP: max_execution_time = 300, memory_limit = 512M (adjust to needs)
  • Disable buffering for streaming endpoints while debugging:
  • fastcgi_buffering off; proxy_buffering off;
  • Add slowlog to PHP-FPM to capture what code is doing when it stalls:
  • request_slowlog_timeout = 5s and slowlog = /var/log/php-fpm/www-slow.log
  • If host won’t change proxy settings, move heavy work to a background queue (Laravel queues) so web requests finish fast; see Laravel queues timeouts docs https://laravel.com/docs/10.x/queues#max-job-attempts-and-timeout.

References for practical configuration: DigitalOcean Nginx+PHP-FPM guide https://www.digitalocean.com/community/tutorials/how-to-configure-nginx-with-php-fpm-on-ubuntu-20-04 and PHP-FPM docs https://www.php.net/manual/en/install.fpm.configuration.php.


What to ask your host (sample support request)

Short, copy-and-paste ticket text:

  • “I have a host-specific issue where an HTTP response starts (≈30 KB) then stalls and the browser retries every ~45s. Please confirm: do you run a reverse-proxy/load-balancer in front of my server? What are your read/write/idle timeouts for HTTP and for FastCGI backends? Do you buffer entire backend responses before sending to clients? Can you temporarily increase proxy_read_timeout/fastcgi_read_timeout to 300s or disable buffering for my site while I debug? Could you run a packet capture for my connection when I reproduce?”

Add this: ask them to check firewall/NAT idle timeouts and any DDoS rules that might reset long connections.


Sources

  1. https://www.php.net/manual/en/function.set-time-limit.php
  2. https://laravel.com/docs/10.x/queues#max-job-attempts-and-timeout
  3. https://developers.cloudflare.com/workers/platform/limits/#worker-timeout
  4. https://docs.nginx.com/nginx/admin-guide/web-server/configuration/
  5. https://www.digitalocean.com/community/tutorials/how-to-configure-nginx-with-php-fpm-on-ubuntu-20-04
  6. https://www.php.net/manual/en/install.fpm.configuration.php
  7. https://stackoverflow.com/questions/28362630/nginx-php-fpm-timeouts
  8. https://www.php.net/manual/en/outcontrol.configuration.php
  9. https://www.php.net/manual/en/function.ob-start.php
  10. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding
  11. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length
  12. https://www.cloudflare.com/learning/performance/what-is-http-chunked-transfer-encoding/
  13. https://www.cloudflare.com/learning/cdn/what-is-a-cdn/
  14. https://www.nginx.com/blog/nginx-and-http-chunked-transfer-encoding/
  15. https://www.wireshark.org/docs/wsug_html_chunked-http.html
  16. https://www.wireshark.org/docs/wsug_html_retransmissions.html
  17. https://www.cloudflare.com/learning/network-layer/what-is-a-firewall/
  18. https://www.cloudflare.com/learning/ddos/what-is-ddos/
  19. https://www.cloudflare.com/learning/network-layer/what-is-tcp-ip/
  20. https://www.cloudflare.com/learning/network-layer/what-is-a-reverse-proxy/
  21. https://docs.docker.com/config/containers/resource_constraints/
  22. https://www.php.net/manual/en/function.memory-get-usage.php
  23. https://www.php.net/manual/en/ini.core.php#ini.memory-limit
  24. https://www.php.net/manual/en/function.ini-get.php
  25. https://www.php.net/manual/en/errorfunc.configuration.php#ini.display-errors

Conclusion

This is almost certainly a host-specific timeout/buffering or network-middlbox problem (reverse-proxy or PHP-FPM worker limits are the usual culprits) causing your Laravel React app stall after the initial ~30 KB; the fastest way to confirm is a streaming test + curl --no-buffer, server logs (nginx + php-fpm slowlog), and a tcpdump/Wireshark capture. If the capture or logs show the proxy or host edge resetting/closing connections (or PHP-FPM being killed), raise the relevant timeouts (fastcgi_read_timeout/proxy_read_timeout, request_terminate_timeout) or disable buffering for testing — and if the host won’t change settings, ask them to run a packet capture and increase their proxy/timeouts or move to a plan that supports long-lived responses.

Authors
Verified by moderation
Moderation
Laravel React App Stalls After 30KB: Fix Timeouts