PHP-FPM #

PHP masih menjadi bahasa pemrograman yang mendominasi ekosistem web global, menjadi motor penggerak bagi sistem manajemen konten terpopuler seperti WordPress, hingga kerangka kerja (framework) modern berkinerja tinggi seperti Laravel dan Symfony. Berbeda dengan server web Apache yang dapat menjalankan PHP sebagai modul internal (mod_php), server web modern seperti Caddy berkomunikasi dengan PHP menggunakan protokol eksternal FastCGI melalui bantuan PHP-FPM (FastCGI Process Manager). Caddy mempermudah integrasi ini secara radikal melalui direktif php_fastcgi yang secara otomatis menangani perutean URL, kompilasi parameter FastCGI, dan deteksi file statis di latar belakang—tugas yang pada server Nginx biasanya membutuhkan belasan baris konfigurasi yang rumit dan rentan kesalahan. Kita akan membahas secara mendalam mekanisme komunikasi FastCGI, membandingkan efisiensi koneksi UNIX Socket vs TCP Socket, menyusun konfigurasi Caddyfile yang kokoh untuk WordPress dan Laravel, memperkuat keamanan dari kebocoran berkas sensitif, melakukan optimasi (tuning) parameter pool PHP-FPM, hingga mengonfigurasi penyimpanan sesi terdistribusi menggunakan Redis.

Cara Kerja PHP di Caddy #

Dalam model penanganan request PHP oleh Caddy, proses dipisah secara tegas berdasarkan tipe berkas yang diminta oleh browser pengguna.

Caddy bertindak sebagai server web tercepat untuk menyajikan aset statis (seperti berkas CSS, JavaScript, gambar PNG/JPEG, dan font). Ketika ada request aset statis masuk, Caddy langsung membaca berkas dari media penyimpanan disk lokal dan mengembalikannya ke pengguna tanpa melibatkan interpreter PHP.

Namun, ketika ada request untuk berkas dengan ekstensi .php (atau rute dinamis yang tidak memiliki file fisik di disk), Caddy akan mengonversi request HTTP tersebut menjadi paket data FastCGI, lalu mengirimkannya ke socket PHP-FPM. Di sana, proses worker pool PHP-FPM akan mengeksekusi script PHP, berinteraksi dengan database MySQL/PostgreSQL, mengompilasi respons HTML, lalu mengembalikan hasilnya ke Caddy untuk dikirimkan kembali ke browser pengguna.

flowchart TD
    Browser["Request HTTP Browser"] --> Caddy["Server Caddy"]
    
    Caddy -->|"Apakah berkas fisik statis?"| Disk["Read langsung dari Disk"]
    Caddy -->|"Tipe berkas .php / Rute dinamis?"| FastCGI["Kirim paket FastCGI (via unix//run/php-fpm.sock)"]
    
    subgraph PHP["Backend PHP"]
        FastCGI --> PHPFPM["PHP-FPM (Eksekusi kode PHP)"]
    end
    
    Disk --> Response["Respons HTML"]
    PHPFPM --> Response

    style Caddy stroke:#0288d1,stroke-width:2px
    style PHP stroke:#37474f,stroke-width:1px,stroke-dasharray:5,5

Pemisahan tugas ini menjamin pemakaian resource CPU server kita tetap efisien karena runtime PHP hanya dipicu untuk menangani logika dinamis yang benar-benar membutuhkannya.


UNIX Socket vs TCP Connection #

Caddy dan PHP-FPM dapat berkomunikasi melalui salah satu dari dua jenis socket: UNIX Domain Socket atau TCP Socket. Memahami perbedaan performa dan cara konfigurasi keduanya sangat krusial saat menyusun infrastruktur web kita.

1. UNIX Domain Socket (Direkomendasikan untuk Server Mandiri) #

UNIX socket merepresentasikan koneksi sebagai berkas socket khusus pada sistem file Linux (misalnya /run/php/php8.3-fpm.sock).

  • Kelebihan: Sangat cepat karena komunikasi terjadi sepenuhnya di dalam kernel Linux tanpa melalui tumpukan protokol jaringan (network stack bypass). Penulisan data langsung disalin di memori RAM.
  • Kekurangan: Hanya dapat digunakan jika Caddy dan PHP-FPM berjalan di dalam satu mesin fisik atau VM yang sama. Sering terjadi masalah hak akses (file permission) berkas socket.

2. TCP Socket (Wajib untuk Multi-Server / Terpisah) #

TCP socket merepresentasikan koneksi sebagai alamat IP dan port jaringan (misalnya 127.0.0.1:9000).

  • Kelebihan: Memungkinkan Caddy dan PHP-FPM berjalan di mesin terpisah (misalnya Caddy di depan bertindak sebagai gateway, dan PHP-FPM di server backend khusus aplikasi).
  • Kekurangan: Memiliki overhead latensi jaringan tambahan karena setiap data harus dikemas menjadi paket TCP/IP lokal, bahkan jika berjalan di mesin yang sama.

Mengatasi Masalah “Permission Denied” pada UNIX Socket #

Masalah klasik yang sering ditemui saat baru pertama kali mengintegrasikan Caddy dengan UNIX socket PHP-FPM adalah error 502 Bad Gateway di browser, dan log kesalahan dial unix /run/php/php8.3-fpm.sock: connect: permission denied pada log Caddy.

Hal ini terjadi karena berkas socket dibuat oleh PHP-FPM dengan hak akses terbatas yang hanya dapat dibaca oleh user default Apache/Nginx (www-data), sementara proses Caddy berjalan di bawah user sendiri (caddy).

Untuk menyelesaikannya, kita harus mengubah konfigurasi pemilik socket di dalam berkas konfigurasi pool PHP-FPM (biasanya di /etc/php/8.3/fpm/pool.d/www.conf):

; /etc/php/8.3/fpm/pool.d/www.conf
; ANTI-PATTERN: Membiarkan owner default www-data saat menggunakan Caddy
; listen.owner = www-data
; listen.group = www-data

; BENAR: Atur pemilik socket agar sesuai dengan user yang menjalankan proses Caddy
listen.owner = caddy
listen.group = caddy
listen.mode = 0660

Setelah mengubah konfigurasi tersebut, lakukan restart pada service PHP-FPM:

sudo systemctl restart php8.3-fpm

Konfigurasi Dasar PHP #

Setelah hak akses socket dipastikan aman, kita dapat menulis berkas Caddyfile dasar untuk menjalankan PHP. Direktif php_fastcgi di Caddy secara otomatis melakukan trik pintar: mendeteksi index berkas index.php secara otomatis, memblokir akses ke berkas tersembunyi, dan meneruskan request dinamis:

# Konfigurasi dasar PHP-FPM di Caddy
example.com {
    # Tentukan direktori root tempat kode PHP kita disimpan
    root * /var/www/html
    
    # Teruskan request PHP ke socket UNIX PHP-FPM
    php_fastcgi unix//run/php/php8.3-fpm.sock
    
    # Aktifkan penyajian file statis langsung
    file_server
}

WordPress adalah CMS paling populer di dunia, namun sering kali menjadi target serangan hacker karena banyaknya celah keamanan pada tema atau plugin pihak ketiga. Kita harus memperkuat (hardening) konfigurasi Caddyfile untuk memblokir akses ke berkas sensitif WordPress, serta mengonfigurasi URL ramah mesin pencari (pretty permalinks):

# Konfigurasi WordPress aman di Caddy
wordpress.example.com {
    root * /var/www/wordpress
    encode zstd gzip
    
    # 1. Hardening Keamanan: Blokir akses langsung ke file sensitif
    @blocked {
        path /wp-admin/includes/*
        path /wp-includes/theme-compat/*
        path /wp-includes/js/tinymce/langs/*
        path *.sql
        path */.git/*
        path */.env*
        path /xmlrpc.php # Blokir serangan brute force XML-RPC
        path /wp-config.php # Lindungi kredensial database
    }
    respond @blocked "Akses Ditolak!" 403
    
    # 2. WordPress Pretty Permalinks & Core Routing.
    # php_fastcgi secara otomatis mengalihkan request URL non-fisik ke index.php
    php_fastcgi unix//run/php/php8.3-fpm.sock
    
    # 3. Sajikan aset statis WordPress
    file_server
    
    # Fallback rewrite untuk penanganan rute virtual WordPress
    try_files {path} {path}/ /index.php?{query}
}

Laravel Optimization #

Laravel adalah framework PHP modern yang menggunakan pola satu gerbang masuk (Front Controller Pattern) di mana seluruh request dialirkan melalui satu berkas /public/index.php. Kita harus memastikan direktori root menunjuk ke folder public/, bukan ke folder root proyek Laravel kita, demi mencegah tereksposnya berkas sensitif .env ke publik:

# Konfigurasi produksi optimal untuk Laravel
laravel.example.com {
    # PENTING: Arahkan root ke folder public Laravel
    root * /var/www/laravel/public
    encode zstd gzip
    
    # Sisipkan header keamanan standar
    header {
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
        -Server
        -X-Powered-By
    }
    
    # Salurkan request ke PHP-FPM
    php_fastcgi unix//run/php/php8.3-fpm.sock
    
    # Aktifkan pembacaan file statis
    file_server
    
    # Laravel routing fallback: Teruskan seluruh rute virtual ke index.php
    try_files {path} {path}/ /index.php?{query}
}

Keamanan File Sensitif PHP #

Selain mengandalkan pemisahan folder publik di Laravel, terkadang kita men-deploy aplikasi PHP warisan (legacy PHP) yang menyimpan berkas konfigurasi di folder yang sama dengan berkas publik. Kita wajib menerapkan blokade berlapis pada Caddyfile untuk mencegah pengguna mengunduh berkas sensitif tersebut secara langsung:

# Taktik perlindungan data sensitif di Caddyfile
example.com {
    root * /var/www/html
    
    # Blokir akses ke ekstensi berkas berbahaya
    @sensitive {
        path /.env*
        path /config.php
        path /composer.json
        path /composer.lock
        path /package.json
        path *.sql
        path *.log
        path *.bak
    }
    respond @sensitive 403
    
    # ANTI-APTTERN Eksekusi Script di Upload Folder:
    # Peretas sering kali berhasil mengunggah file PHP backdoor (misal shell.php)
    # ke dalam folder upload gambar, lalu mengaksesnya dari browser untuk mengambil alih server.
    # SOLUSI: Blokir eksekusi PHP pada subdirektori uploads dan media.
    @phpInUpload path /uploads/*.php /media/*.php
    respond @phpInUpload "Akses Ditolak: Dilarang Mengeksekusi Script di Folder Upload!" 403
    
    php_fastcgi unix//run/php/php8.3-fpm.sock
    file_server
}

Upload File Besar #

Secara default, baik Caddy maupun PHP-FPM membatasi ukuran request body untuk mencegah serangan DDoS berbasis upload file berukuran raksasa yang dapat menghabiskan memori RAM. Jika aplikasi kita membutuhkan fitur upload file besar (misalnya aplikasi file manager atau unggah video hingga 100MB), kita harus mengubah konfigurasi di kedua sisi:

1. Konfigurasi Caddyfile (Sisi Server Web) #

# Konfigurasi batas ukuran request Caddy
uploads.example.com {
    root * /var/www/uploads-app
    
    php_fastcgi unix//run/php/php8.3-fpm.sock {
        # Tingkatkan timeout membaca respons agar koneksi tidak diputus saat upload lambat
        read_timeout 300s
    }
    
    # Batasi ukuran request body maksimal 100 Megabyte
    request_body {
        max_size 100mb
    }
    
    file_server
}

2. Konfigurasi php.ini (Sisi PHP-FPM Runtime) #

Kita juga harus menaikkan batas memori dan ukuran upload di dalam file /etc/php/8.3/fpm/php.ini:

; /etc/php/8.3/fpm/php.ini
upload_max_filesize = 100M
post_max_size = 105M        ; Harus sedikit lebih besar dari upload_max_filesize
max_execution_time = 300    ; Batas waktu eksekusi skrip (5 menit)
max_input_time = 300        ; Batas waktu membaca input data upload
memory_limit = 256M         ; Batas penggunaan RAM per worker

PHP-FPM Pool Tuning berdasarkan Kapasitas RAM #

Menentukan jumlah proses pekerja (worker processes) PHP-FPM yang tepat sangat penting untuk menjaga performa server. Jika jumlah worker terlalu sedikit, request pengguna akan mengantre lama dan memicu error timeout. Namun jika jumlah worker terlalu banyak, server akan kehabisan memori RAM dan mengalami crash total.

Kita dapat mengatur parameter manajemen proses dinamis di /etc/php/8.3/fpm/pool.d/www.conf:

; /etc/php/8.3/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 50        ; Jumlah maksimal worker process yang boleh aktif
pm.start_servers = 5        ; Jumlah worker yang langsung dibuat saat FPM dinyalakan
pm.min_spare_servers = 5    ; Jumlah minimal worker idle yang harus selalu siaga
pm.max_spare_servers = 35   ; Jumlah maksimal worker idle yang boleh siaga
pm.max_requests = 500       ; Matikan & buat ulang worker setelah memproses 500 request (anti leak)

Rumus Menghitung pm.max_children Secara Presisi #

Jangan menebak nilai pm.max_children. Kita dapat menghitungnya secara ilmiah berdasarkan sisa kapasitas memori RAM server kita.

  1. Gunakan perintah shell berikut untuk memantau rata-rata memori RAM yang digunakan oleh satu proses worker PHP-FPM di server kita:

    ps -eo pid,pmem,rss,comm | grep php-fpm | awk '{print $3}' | \
    awk 'BEGIN{s=0}{s+=$1}END{print "Rata-rata penggunaan RAM per worker: " s/NR/1024 " MB"}'
    

    Misalnya, rata-rata konsumsi per worker adalah 40 MB.

  2. Gunakan rumus berikut untuk menghitung batas maksimal anak proses: [\text{pm.max_children} = \frac{\text{Total RAM Server} - \text{RAM untuk Sistem & Caddy}}{\text{Rata-rata RAM per Worker PHP-FPM}}] Jika server kita memiliki RAM 4 GB (4096 MB), dan kita menyisihkan 1 GB (1024 MB) untuk sistem operasi, database, dan Caddy: [\text{pm.max_children} = \frac{4096 - 1024}{40} = 76 \text{ workers}]


PHP dengan Redis Session (Distributed Sessions) #

Jika kita melakukan skala horizontal (horizontal scaling) dengan men-deploy aplikasi PHP di beberapa server virtual di belakang satu load balancer Caddy, masalah pelik akan muncul jika data sesi pengguna (session data) disimpan sebagai file lokal di masing-masing server. Pengunjung yang terarah ke Server A saat login akan mendadak ter-logout saat request berikutnya diarahkan ke Server B karena Server B tidak memiliki file sesi pengunjung tersebut.

Solusinya adalah menyimpan seluruh sesi PHP ke dalam server cache Redis terpusat yang diakses bersama oleh semua node PHP-FPM:

; Konfigurasi penyimpanan sesi terpusat di /etc/php/8.3/fpm/conf.d/redis-session.ini
session.save_handler = redis
session.save_path = "tcp://redis-server.local:6379?auth=password_redis_rahasia_kita"
session.gc_maxlifetime = 7200 # Masa berlaku sesi: 2 jam

Dengan konfigurasi di atas, seluruh node PHP-FPM kita bersifat stateless (tanpa beban data sesi lokal), sehingga kita dapat dengan mudah menambah atau mengurangi jumlah server PHP kapan saja secara dinamis sesuai fluktuasi trafik web.


Diagram Pipa Pemrosesan Request PHP-FPM oleh Caddy #

Untuk memberikan visualisasi yang jelas mengenai bagaimana Caddy mengevaluasi rute dan berkomunikasi dengan subsistem PHP-FPM, perhatikan diagram flowchart berikut:

flowchart TD
    A["Request Masuk dari Browser\n(e.g., GET /profile/settings)"] --> B["1. Caddy mengevaluasi named matchers & rules"]
    
    B --> C{"2. Apakah rute cocok dengan\nfile statis yang ada di disk?"}
    
    C -- "Ya" --> D["3. Caddy langsung sajikan berkas\n(Tanpa memicu PHP-FPM)"]
    D --> E["Selesai"]
    
    C -- "Tidak" --> F["4. Picu direktif php_fastcgi"]
    F --> G["5. Lakukan internal rewrite ke index.php\n(Laravel/WordPress front controller pattern)"]
    
    G --> H["6. Kemas request menjadi data FastCGI\n(Definisikan SCRIPT_FILENAME & parameter)"]
    
    H --> I["7. Kirim paket via socket UNIX\n(unix//run/php/php8.3-fpm.sock)"]
    
    I --> J["8. Pool Manager PHP-FPM\n(Menerima data & menugaskan worker process)"]
    J --> K["9. Eksekusi kode runtime PHP\n(Akses database, render template, dll.)"]
    
    K --> L["10. Kembalikan output respons ke socket Caddy"]
    L --> M["11. Caddy mengompresi respons (Gzip/Zstd)\ndan mengirimkan ke browser Klien"]
    M --> E

Ringkasan #

  • Kemudahan Konfigurasi: Direktif php_fastcgi di Caddyfile menyederhanakan konfigurasi PHP dengan mengotomatiskan rewrite rute, penanganan index, dan pemetaan FastCGI secara native.
  • Performa UNIX Socket: Gunakan UNIX socket (unix//run/php/php8.3-fpm.sock) alih-alih port TCP 127.0.0.1:9000 untuk performa I/O memori lokal yang lebih cepat.
  • Solusi Izin Akses: Atur properti pemilik socket listen.owner = caddy dan listen.group = caddy di konfigurasi pool PHP-FPM untuk menghindari error permission denied.
  • Perlindungan Berkas: Selalu blokir akses langsung ke berkas sensitif (.env, .git, wp-config.php, composer.json) menggunakan named matcher dan aksi respond 403 di Caddyfile.
  • Hardening Direktori Upload: Cegah eksekusi backdoor web shell dengan memblokir eksekusi berkas .php di dalam subdirektori uploads dan media.
  • Kalkulasi Worker RAM: Tentukan batas maksimal pm.max_children berdasarkan rata-rata konsumsi memori satu proses worker PHP-FPM untuk mencegah kehabisan RAM.
  • Skala Horizontal: Gunakan Redis sebagai repositori penyimpanan sesi PHP terpusat agar klaster server PHP-FPM kita dapat bersifat stateless.

← Sebelumnya: Node.js App   Berikutnya: Python WSGI →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact