Bali, Indonesia
Insights

Mengira Naikkan max_execution_time Cukup untuk Hilangkan Error 504 di Nginx

October 22, 2026
0:00
Saat situs di belakang Nginx menampilkan 504 Gateway Timeout, refleks yang paling sering muncul adalah membuka php.ini lalu menaikkan 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.
Kode 504 artinya gateway sudah menunggu terlalu lama dan menyerah. Di setup ini, gateway-nya Nginx. Nginx meneruskan request ke upstream, yaitu proses di belakangnya yang mengerjakan request. Upstream itu bisa PHP-FPM lewat 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.
Cara paling gampang membayangkannya: setiap layer memegang stopwatch sendiri, dan stopwatch yang paling pendek yang menang. Urutannya kira-kira begini, dari luar ke dalam:
  1. CDN, load balancer, atau reverse proxy di depan server (kalau ada). Masing-masing punya batas waktu sendiri, jadi cek dokumentasi layanannya.
  2. Nginx, lewat fastcgi_read_timeout atau proxy_read_timeout.
  3. PHP-FPM, lewat request_terminate_timeout di 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.
  4. PHP, lewat max_execution_time atau set_time_limit() yang dipanggil script atau plugin.
  5. Di bawahnya lagi ada timeout koneksi database dan timeout HTTP client kalau script memanggil API luar.
Rule of thumb yang aku pakai: layer dalam diberi batas sedikit lebih kecil daripada layer luar. Tujuannya supaya yang gagal duluan adalah layer yang meninggalkan pesan paling jelas. Contoh ilustrasi saja: 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.
Yang paling sering bikin bingung: di Linux, 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.
Perintah di bawah ini read-only, aman dijalankan di server produksi. Path log dan pool di contoh ini khas Debian/Ubuntu, jadi sesuaikan dengan setup kamu.
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/
Kalau perintah pertama tidak mengembalikan apa-apa, Nginx memakai default 60 detik. Di error log, baris yang relevan biasanya berisi 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.
Cara terbaik untuk benar-benar paham urutan ini adalah melihatnya sendiri. Lakukan di VPS uji, jangan di server produksi, karena script di bawah sengaja menahan worker PHP-FPM. Script kedua juga memakai satu core CPU penuh selama berjalan.
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';
Simpan masing-masing sebagai file terpisah di document root situs uji. Dengan nilai default (max_execution_time 30, fastcgi_read_timeout 60), hasil yang biasanya terlihat:
  1. sleep.php berakhir dengan 504 sekitar detik ke-60. Error log Nginx mencatat upstream timed out. PHP tidak protes karena waktu sleep() tidak dihitung.
  2. busy.php berakhir sekitar detik ke-30 dengan fatal error maximum execution time, karena kali ini PHP benar-benar memakai CPU.
  3. Naikkan fastcgi_read_timeout ke 120s, cek dengan nginx -t, lalu reload. Sekarang sleep.php selesai normal.
  4. Isi request_terminate_timeout dengan nilai lebih kecil dari 90 detik di pool uji, lalu reload PHP-FPM. sleep.php sekarang biasanya berakhir dengan 502, dan log FPM mencatat worker dihentikan.
Dari empat percobaan ini terlihat jelas bahwa tiap layer punya stopwatch sendiri, dan kode error yang muncul tergantung siapa yang menyerah duluan. Setelah selesai, hapus kedua file dan kembalikan konfigurasi pool ke nilai semula. Jadi lain kali muncul 504, jangan langsung buka php.ini. Baca error log Nginx dulu untuk tahu siapa yang memutus koneksi. Kalau memang perlu waktu lebih, sesuaikan timeout sebagai satu set dengan layer dalam sedikit lebih kecil dari layer luar. Request yang butuh waktu sangat lama sebaiknya dianggap tanda bahwa pekerjaannya perlu dipindah ke background.