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:

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

Fungsi tiap kelompok direktif adalah sebagai berikut:

DirektifFungsi
user, groupIdentitas Unix yang dipakai worker — prinsip least privilege berlaku di sini
listenAlamat socket yang menunggu permintaan (dibahas di bagian berikutnya)
pm + pm.max_children + pm.start_servers + pm.min_spare_servers + pm.max_spare_serversKebijakan jumlah worker
pm.status_pathJalur halaman status internal
slowlog, request_slowlog_timeoutPencatatan permintaan yang lambat

Direktif pm memiliki tiga mode dengan karakter berbeda:

ModePerilakuCocok untuk
staticJumlah worker tetap sejak startBeban stabil dan RAM pas-pasan
dynamicWorker diskalakan antara batas minimum dan maksimumMayoritas aplikasi
ondemandWorker dibuat hanya saat ada permintaanTrafik 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
<?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:

text
listen: /run/php/php8.4-fpm.sock
pm: dynamic
pm.max_children: 50
request_slowlog_timeout: 5s

Mari kita bedah kodenya:

  1. Baris 2-15 (heredoc $poolWww): isi pool ditanam sebagai string agar contoh ini dapat berjalan mandiri — pada skrip audit nyata, Anda menggantinya dengan file_get_contents('/etc/php/8.4/fpm/pool.d/www.conf').
  2. Baris 17 (parse_ini_string(...)): argumen kedua true mempertahankan struktur seksi sehingga kunci pm.max_children berada di bawah seksi www; INI_SCANNER_RAW menjaga nilai tetap diperlakukan sebagai string mentah — tanpa ini, nilai 5s akan ditolak parser karena bukan konstanta INI yang valid.
  3. Baris 19-22: nilai-nilai penting terbaca utuh, termasuk request_slowlog_timeout berisi 5s yang 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
<?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:

text
102
51
25
204

Mari kita amati perhitungannya:

  1. Baris 2-9 (function hitungMaxChildren): mengurangkan porsi sistem dari total RAM, memvalidasi masukan, lalu membagi dengan intdiv() yang membulatkan ke bawah — pembulatan ke atas berisiko mengklaim RAM yang tidak ada.
  2. 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 serverRAM sistem & layanan lainpm.max_children
2 GB512 MB25
4 GB1 GB51
8 GB2 GB102
16 GB4 GB204

Soket Unix vs TCP #

Direktif listen menentukan bagaimana web server berbicara dengan FPM, dan ada dua pilihan utama:

AspekUnix domain socketTCP 127.0.0.1:9000
AlamatBerkas di filesystem, mis. /run/php/php8.4-fpm.sockPort di loopback
KecepatanSedikit lebih cepat (tanpa susunan protokol TCP)Sedikit lebih lambat
JangkauanHanya mesin yang samaBisa lintas mesin/container
PengamananIzin 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.

ini
#-- Alternatif untuk listen:
listen = /run/php/php8.4-fpm.sock
listen = 127.0.0.1:9000

Status 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 statusArtinya
active processesWorker yang sedang mengeksekusi permintaan
idle processesWorker menganggur
max children reachedSeberapa sering permintaan ditolak karena semua slot terpakai — angka selain nol berarti pm.max_children perlu ditinjau
slow requestsPermintaan yang melampaui request_slowlog_timeout
listen queuePermintaan 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:

text
[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:88

Contoh 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.

bash
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 menerapkan

Jadikan 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 .

Referensi #