max_execution_time dari 30 jadi 300. Setelah PHP-FPM di-reload, halaman yang sama tetap berhenti di 504, kira-kira di detik yang sama seperti sebelumnya. Bukan karena setting PHP-nya tidak terbaca. Yang memutus koneksi memang bukan PHP.
Catatan ini aku tulis sebagai bahan belajar: apa yang sebenarnya terjadi, bagian mana yang gampang disalahpahami, dan cara membuktikannya sendiri di VPS uji.
Kenapa 504 tetap muncul padahal PHP sudah diberi waktu lebih
fastcgi_pass, atau web server lain seperti Apache atau aplikasi Node lewat proxy_pass.
Nginx punya stopwatch sendiri untuk menunggu jawaban upstream. Untuk FastCGI namanya fastcgi_read_timeout, untuk proxy HTTP namanya proxy_read_timeout. Keduanya default 60 detik kalau tidak diatur. Nginx sama sekali tidak tahu berapa nilai max_execution_time di PHP. Jadi kalau sebuah script butuh 90 detik, Nginx tetap berhenti menunggu di detik ke-60, walaupun PHP diizinkan jalan 300 detik.
Ada petunjuk yang sering terlewat: kode error-nya sendiri sudah memberi tahu layer mana yang gagal. Kalau PHP yang kena batas max_execution_time duluan, PHP menghasilkan fatal error Maximum execution time of 30 seconds exceeded. Yang terlihat di browser biasanya 500 atau halaman kosong, tergantung setting display_errors, bukan 504. Begitu yang muncul 504, kecurigaan pertama sebaiknya ke timeout di Nginx atau layer di depannya, bukan ke php.ini.
Urutan timeout dari browser sampai script PHP
- CDN, load balancer, atau reverse proxy di depan server (kalau ada). Masing-masing punya batas waktu sendiri, jadi cek dokumentasi layanannya.
- Nginx, lewat
fastcgi_read_timeoutatauproxy_read_timeout. - PHP-FPM, lewat
request_terminate_timeoutdi konfigurasi pool. Default-nya 0 alias nonaktif. Kalau diisi, FPM akan mematikan worker yang melewati batas ini, dan Nginx biasanya mencatatnya sebagai 502, bukan 504. - PHP, lewat
max_execution_timeatauset_time_limit()yang dipanggil script atau plugin. - Di bawahnya lagi ada timeout koneksi database dan timeout HTTP client kalau script memanggil API luar.
max_execution_time 120 detik, request_terminate_timeout sedikit di atasnya, fastcgi_read_timeout sedikit di atasnya lagi, dan CDN paling besar. Dengan susunan seperti itu, request yang kelamaan akan berakhir dengan fatal error PHP yang menyebut file dan baris, bukan 504 generik yang tidak menjelaskan apa-apa.
Satu detail kecil: fastcgi_read_timeout dihitung sebagai jeda di antara dua operasi baca dari upstream, bukan total durasi response. Dalam praktiknya, script PHP biasa yang baru mengirim output di akhir terlihat diam sepanjang proses, jadi efeknya hampir sama dengan batas total.
Ada juga konsekuensi yang jarang disadari. Saat Nginx menyerah duluan, worker PHP-FPM tidak ikut berhenti. Script tetap jalan sampai selesai atau sampai dimatikan. Kalau pengunjung me-refresh halaman, request baru mengambil worker lain. Di situs yang ramai, pola ini bisa membuat pool kehabisan worker, dan 504 yang awalnya cuma di satu halaman berubah jadi lambat di semua halaman. Topik itu dibahas lebih dalam di Unraveling PHP-FPM Pool Starvation in High-Concurrency WordPress Hosting. Karena itu request_terminate_timeout tetap berguna sebagai jaring pengaman.
Hal yang gampang disalahpahami soal max_execution_time
max_execution_time hanya menghitung waktu eksekusi script itu sendiri. Waktu yang dipakai untuk system call, operasi stream, query database, atau sleep() tidak ikut dihitung. Ini tertulis di dokumentasi PHP, dan perilakunya berbeda di Windows. Artinya script yang menunggu API luar yang lambat selama dua menit bisa saja tidak pernah menyentuh batas 30 detik, padahal Nginx melihat dua menit tanpa jawaban. Menaikkan max_execution_time tidak membantu, dan menurunkannya juga tidak melindungi apa-apa.
Kedua, nilai yang kamu cek belum tentu nilai yang dipakai. Perintah php -i di terminal membaca konfigurasi CLI, yang sering memakai php.ini berbeda dan default max_execution_time 0. Nilai untuk web bisa ditimpa di beberapa tempat: php_admin_value di pool FPM, file .user.ini di folder situs, editor INI per domain di control panel, atau plugin yang memanggil set_time_limit(). Cara paling jujur untuk tahu nilai efektifnya adalah mengeceknya dari request web, misalnya halaman phpinfo sementara yang langsung dihapus setelah dicek.
Ketiga, menaikkan semua timeout setinggi mungkin terasa seperti solusi, tapi sering cuma menyembunyikan masalah. Halaman yang butuh tiga menit untuk dimuat biasanya punya penyebab nyata: query lambat, panggilan API luar yang macet, atau pekerjaan berat seperti import dan export yang memang tidak cocok dijalankan lewat request web. Pekerjaan seperti itu lebih aman dipindah ke background lewat WP-CLI atau cron. Kalau memakai system cron, pastikan juga cron-nya benar-benar jalan, seperti dijelaskan di How to Verify Your WordPress System Cron Job Is Actually Running.
Di meja support, pola ini cukup sering muncul. Customer sudah menaikkan nilai di php.ini lewat panel, error tetap ada, lalu mengira hosting-nya bermasalah. Hal pertama yang biasanya dicek engineer bukan php.ini, tapi error log Nginx.
Cara cek siapa yang sebenarnya memutus koneksi
BASH
# Lihat nilai timeout yang benar-benar dimuat Nginx, termasuk dari file include
sudo nginx -T 2>/dev/null | grep -nE 'fastcgi_read_timeout|proxy_read_timeout'
# Cari jejak timeout di error log Nginx
sudo grep 'upstream timed out' /var/log/nginx/error.log | tail -n 20
# Cek apakah pool PHP-FPM punya request_terminate_timeout
sudo grep -R 'request_terminate_timeout' /etc/php/*/fpm/pool.d/upstream timed out (110: Connection timed out) while reading response header from upstream. Perhatikan field upstream di baris yang sama. Kalau isinya diawali fastcgi://, berarti yang perlu disesuaikan fastcgi_read_timeout. Kalau isinya http:// ke port lokal, misalnya karena Nginx berada di depan Apache, berarti yang berlaku proxy_read_timeout.
Kalau ternyata yang muncul 502 dan log PHP-FPM menyebut worker dihentikan karena waktu habis, berarti request_terminate_timeout yang memotong lebih dulu. Untuk tahu fungsi mana yang membuat script tertahan, PHP-FPM punya request_slowlog_timeout yang bisa diaktifkan per pool untuk mencatat stack trace request yang lambat.
Saat menyesuaikan, ubah sebagai satu set. Misalnya fastcgi_read_timeout 120s; di blok location yang menangani PHP, lalu jalankan sudo nginx -t sebelum reload. Setelah itu sesuaikan max_execution_time dan request_terminate_timeout supaya urutannya tetap dalam lebih kecil dari luar.
Latihan aman di VPS uji
PHP
<?php
// File 1: sleep.php - menunggu tanpa memakai CPU
sleep(90);
echo 'selesai tidur';
// File 2: busy.php - sibuk memakai CPU selama 90 detik
// $end = time() + 90;
// while (time() < $end) {}
// echo 'selesai sibuk';max_execution_time 30, fastcgi_read_timeout 60), hasil yang biasanya terlihat:
sleep.phpberakhir dengan 504 sekitar detik ke-60. Error log Nginx mencatat upstream timed out. PHP tidak protes karena waktusleep()tidak dihitung.busy.phpberakhir sekitar detik ke-30 dengan fatal error maximum execution time, karena kali ini PHP benar-benar memakai CPU.- Naikkan
fastcgi_read_timeoutke 120s, cek dengannginx -t, lalu reload. Sekarangsleep.phpselesai normal. - Isi
request_terminate_timeoutdengan nilai lebih kecil dari 90 detik di pool uji, lalu reload PHP-FPM.sleep.phpsekarang biasanya berakhir dengan 502, dan log FPM mencatat worker dihentikan.
