Saat development, kita menjalankan PHP lewat CLI atau built-in web server php -S — cukup untuk satu pengguna di satu laptop. Produksi menuntut hal yang berbeda: ratusan permintaan per detik, proses yang hidup terus-menerus, dan pemisahan tugas antara web server dan interpreter. PHP-FPM (FastCGI Process Manager) adalah jawaban standar untuk semua itu — komponen yang menjalankan kode PHP Anda sebagai layanan terkelola di belakang Nginx maupun Apache. Chapter ini membedah cara kerjanya, anatomi konfigurasi pool, dan cara menyetelnya agar sesuai ukuran server Anda.
Apa Itu PHP-FPM #
Protokol FastCGI memungkinkan web server menerima HTTP lalu meneruskan eksekusi PHP ke proses lain lewat socket. Ini beda dari CGI klasik yang membangkitkan proses PHP baru untuk setiap permintaan (lambat karena biaya start berulang), dan juga beda dari mod_php yang menanamkan interpreter langsung di dalam proses web server (boros karena setiap koneksi statis ikut memuat interpreter). FPM berada di tengah: sekumpulan proses PHP yang sudah hidup sejak awal, siap menerima permintaan lewat socket tanpa biaya start ulang.
Pemisahan proses seperti ini membawa tiga keuntungan praktis. Pertama, PHP dan web server dapat direstart secara terpisah — Anda bisa menerapkan perubahan konfigurasi PHP tanpa mengganggu koneksi web server. Kedua, beberapa pool dengan konfigurasi berbeda dapat berjalan pada satu mesin: pool untuk aplikasi utama dan pool untuk cron berat dengan batas memori berbeda. Ketiga, identitas Unix tiap pool dapat dibedakan sehingga aplikasi yang satu tidak membaca berkas aplikasi lainnya.
Model Master dan Worker #
Arsitektur FPM terdiri atas satu master process dan sejumlah worker. Master membaca konfigurasi, membuat worker, lalu mengawasi kesehatannya — bila seorang worker mati (misalnya karena fatal error atau out of memory), master menggantinya dengan worker baru. Workerlah yang benar-benar mengeksekusi kode PHP, dan setiap worker menangani tepat satu permintaan dalam satu waktu.
Diagram di atas merangkum alurnya: web server tidak pernah menyentuh interpreter — master menerima permintaan dari socket lalu menugaskan worker yang kosong, dan hasil eksekusi dialirkan kembali sebagai respons HTTP. Karena satu worker hanya memproses satu permintaan sekaligus, jumlah worker (yang diatur lewat konfigurasi pool) adalah batas konkurensi aktual aplikasi Anda — inilah angka yang kita hitung di bagian pm.max_children nanti.
Anatomi Pool di www.conf #
Konfigurasi FPM tersusun per pool dalam berkas INI, biasanya di /etc/php/8.4/fpm/pool.d/www.conf. Berikut versi ringkasnya yang memuat direktif paling penting:
[www]
user = www-data
group = www-data
listen = /run/php/php8.4-fpm.sock
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 8
pm.status_path = /fpm-status
slowlog = /var/log/php-fpm/www-slow.log
request_slowlog_timeout = 5sFungsi tiap kelompok direktif adalah sebagai berikut:
| Direktif | Fungsi |
|---|---|
user, group | Identitas Unix yang dipakai worker — prinsip least privilege berlaku di sini |
listen | Alamat socket yang menunggu permintaan (dibahas di bagian berikutnya) |
pm + pm.max_children + pm.start_servers + pm.min_spare_servers + pm.max_spare_servers | Kebijakan jumlah worker |
pm.status_path | Jalur halaman status internal |
slowlog, request_slowlog_timeout | Pencatatan permintaan yang lambat |
Direktif pm memiliki tiga mode dengan karakter berbeda:
| Mode | Perilaku | Cocok untuk |
|---|---|---|
static | Jumlah worker tetap sejak start | Beban stabil dan RAM pas-pasan |
dynamic | Worker diskalakan antara batas minimum dan maksimum | Mayoritas aplikasi |
ondemand | Worker dibuat hanya saat ada permintaan | Trafik jarang, aplikasi kecil |
Pada mode dynamic yang paling umum dipakai, FPM memulai dengan pm.start_servers worker, menjaga jumlah worker menganggur (spare) di antara pm.min_spare_servers dan pm.max_spare_servers, dan tidak pernah melebihi pm.max_children. Karena formatnya INI, berkas pool ini bisa dibaca langsung oleh PHP — berguna saat Anda menulis skrip audit konfigurasi:
<?php
$poolWww = <<<'INI'
[www]
user = www-data
group = www-data
listen = /run/php/php8.4-fpm.sock
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 8
pm.status_path = /fpm-status
slowlog = /var/log/php-fpm/www-slow.log
request_slowlog_timeout = 5s
INI;
$konfig = parse_ini_string($poolWww, true, INI_SCANNER_RAW);
echo "listen: " . $konfig['www']['listen'] . "\n";
echo "pm: " . $konfig['www']['pm'] . "\n";
echo "pm.max_children: " . $konfig['www']['pm.max_children'] . "\n";
echo "request_slowlog_timeout: " . $konfig['www']['request_slowlog_timeout'] . "\n";Output yang Dihasilkan:
listen: /run/php/php8.4-fpm.sock
pm: dynamic
pm.max_children: 50
request_slowlog_timeout: 5sMari kita bedah kodenya:
- Baris 2-15 (heredoc
$poolWww): isi pool ditanam sebagai string agar contoh ini dapat berjalan mandiri — pada skrip audit nyata, Anda menggantinya denganfile_get_contents('/etc/php/8.4/fpm/pool.d/www.conf'). - Baris 17 (
parse_ini_string(...)): argumen keduatruemempertahankan struktur seksi sehingga kuncipm.max_childrenberada di bawah seksiwww;INI_SCANNER_RAWmenjaga nilai tetap diperlakukan sebagai string mentah — tanpa ini, nilai5sakan ditolak parser karena bukan konstanta INI yang valid. - Baris 19-22: nilai-nilai penting terbaca utuh, termasuk
request_slowlog_timeoutberisi5syang tidak akan lolos pada mode parsing default.
Menghitung pm.max_children #
Nilai pm.max_children yang salah adalah penyebab paling umum dari insiden “server tiba-tiba mati” pada aplikasi PHP. Rumus dasarnya membagi RAM yang tersedia untuk PHP dengan konsumsi memori rata-rata satu worker: (RAM total − RAM untuk OS dan layanan lain) ÷ RAM rata-rata per worker. Konsumsi per worker diukur dari aplikasi Anda sendiri — pantau memory_get_usage(true) di akhir permintaan, atau lihat kolom RSS proses worker lewat ps saat beban puncak.
<?php
function hitungMaxChildren(int $totalRamMb, int $ramSistemMb, int $ramPerProsesMb): int
{
$ramUntukPhp = $totalRamMb - $ramSistemMb;
if ($ramUntukPhp <= 0 || $ramPerProsesMb <= 0) {
throw new InvalidArgumentException('Nilai RAM tidak valid');
}
return intdiv($ramUntukPhp, $ramPerProsesMb);
}
// Server 8 GB: 2 GB untuk OS + MySQL, rata-rata worker 60 MB
echo hitungMaxChildren(8192, 2048, 60) . "\n";
echo hitungMaxChildren(4096, 1024, 60) . "\n";
echo hitungMaxChildren(2048, 512, 60) . "\n";
echo hitungMaxChildren(16384, 4096, 60) . "\n";Output yang Dihasilkan:
102
51
25
204Mari kita amati perhitungannya:
- Baris 2-9 (function
hitungMaxChildren): mengurangkan porsi sistem dari total RAM, memvalidasi masukan, lalu membagi denganintdiv()yang membulatkan ke bawah — pembulatan ke atas berisiko mengklaim RAM yang tidak ada. - Baris 11-15: empat skenario ukuran server dengan asumsi yang sama (worker rata-rata 60 MB) menghasilkan batas yang jauh berbeda; hasilnya dirangkum pada tabel di bawah.
Dengan asumsi worker 60 MB, hasil perhitungan di atas membentuk tabel patokan berikut:
| RAM server | RAM sistem & layanan lain | pm.max_children |
|---|---|---|
| 2 GB | 512 MB | 25 |
| 4 GB | 1 GB | 51 |
| 8 GB | 2 GB | 102 |
| 16 GB | 4 GB | 204 |
Soket Unix vs TCP #
Direktif listen menentukan bagaimana web server berbicara dengan FPM, dan ada dua pilihan utama:
| Aspek | Unix domain socket | TCP 127.0.0.1:9000 |
|---|---|---|
| Alamat | Berkas di filesystem, mis. /run/php/php8.4-fpm.sock | Port di loopback |
| Kecepatan | Sedikit lebih cepat (tanpa susunan protokol TCP) | Sedikit lebih lambat |
| Jangkauan | Hanya mesin yang sama | Bisa lintas mesin/container |
| Pengamanan | Izin berkas (listen.owner, listen.group) | Firewall; jangan pernah ekspos ke publik |
Pada satu mesin, soket Unix adalah pilihan bawaan yang baik: lebih cepat dan otomatis terlindungi oleh izin berkas. TCP menjadi pilihan ketika PHP berjalan di mesin atau container terpisah dari web server — skala di mana selisih kinerja kecil itu tidak lagi berarti dibanding fleksibilitas yang didapat.
#-- Alternatif untuk listen:
listen = /run/php/php8.4-fpm.sock
listen = 127.0.0.1:9000Status Page dan Slow Log #
Dua fitur bawaan FPM membantu Anda memahami apa yang terjadi di bawah beban. Pertama, halaman status: dengan pm.status_path = /fpm-status, FPM menyajikan laporan ringkas setiap kali jalur tersebut diminta (web server perlu meneruskannya — kita konfigurasikan di chapter Nginx dan PHP
). Aktifkan hanya di jaringan internal atau lindungi dengan autentikasi, karena laporan ini membocorkan detail internal aplikasi.
| Metrik di halaman status | Artinya |
|---|---|
active processes | Worker yang sedang mengeksekusi permintaan |
idle processes | Worker menganggur |
max children reached | Seberapa sering permintaan ditolak karena semua slot terpakai — angka selain nol berarti pm.max_children perlu ditinjau |
slow requests | Permintaan yang melampaui request_slowlog_timeout |
listen queue | Permintaan yang menunggu karena tidak ada worker kosong |
Kedua, slow log: ketika sebuah permintaan berjalan lebih lama dari request_slowlog_timeout, FPM menuliskan stack trace proses tersebut ke berkas slowlog — begitu ambang waktu terlampaui, tanpa menunggu permintaan selesai. Isinya kira-kira seperti berikut:
[15-Jan-2026 08:12:04] [pool www] pid 12345
script_filename = /var/www/toko/public/index.php
[0x00007ffd] sleep() /var/www/toko/src/Report/Generator.php:42
[0x00007ffd] PDO::query() /var/www/toko/src/Report/Repository.php:88Contoh di atas menunjukkan permintaan lambat karena laporan memanggil sleep() di Generator.php baris 42, lalu query berat di baris berikutnya. Dengan jejak seperti ini, Anda tidak perlu menebak fungsi mana yang membuat halaman “kadang lambat” — cukup buka berkas dan baris yang disebut, atau naikkan ambang timeout-nya bila laporan memang sengaja dibuat berat.
Strategi Restart #
FPM menyediakan dua cara memuat ulang, dan memilih yang tepat menentukan apakah pengguna merasakan gangguan atau tidak. reload mengirim sinyal ke master agar membuat worker baru dengan konfigurasi terbaru, sementara worker lama dibiarkan menyelesaikan permintaan yang sedang berjalan lebih dulu. restart menghentikan seluruh proses lalu memulai dari nol — lebih menyeluruh tetapi memutus permintaan yang sedang berjalan.
sudo systemctl reload php8.4-fpm # terapkan konfigurasi baru tanpa memutus permintaan berjalan
sudo systemctl restart php8.4-fpm # hentikan lalu jalankan ulang dari nol
sudo php-fpm8.4 -t # validasi berkas konfigurasi sebelum menerapkanJadikan php-fpm8.4 -t kebiasaan sebelum setiap perubahan: kesalahan sintaks pada berkas pool membuat master gagal start saat restart, dan layanan mati total jauh lebih buruk daripada konfigurasi lama yang masih berjalan. Untuk deployment aplikasi (bukan konfigurasi FPM), pola reload-restart yang aman ini akan kita rangkai menjadi strategi zero-downtime pada chapter akhir section ini.
Ringkasan #
PHP-FPM menjalankan aplikasi Anda sebagai kumpulan worker yang diawasi master process, dengan jumlah worker dikendalikan direktif pm dan dibatasi RAM yang tersedia — angka yang wajib dihitung, bukan ditebak. Soket Unix untuk satu mesin, TCP privat untuk lintas mesin; halaman status dan slow log adalah jendela Anda ke perilaku di bawah beban; reload untuk perubahan halus, restart untuk perubahan besar. FPM kini menunggu permintaan di socket-nya — langkah berikutnya adalah menghubungkannya dengan web server yang meneruskan permintaan tersebut, dimulai dari Nginx dan PHP
.