Nginx adalah web server yang mengawal sebagian besar situs PHP berkinerja tinggi di dunia. Berbeda dengan server tradisional yang mengalokasikan satu thread per koneksi, nginx bersifat event-driven: sedikit proses worker menangani ribuan koneksi sekaligus, sehingga ia sangat efisien menyajikan berkas statis dan menjadi pintu depan aplikasi Anda. Dalam arsitektur PHP modern, nginx tidak mengeksekusi kode PHP — ia meneruskan permintaan PHP ke PHP-FPM yang telah kita bahas di chapter sebelumnya. Chapter ini membedah konfigurasi penghubung keduanya, mulai dari server block minimal, pola front controller, hingga header cache dan keamanan.

Sebuah catatan jujur tentang lingkungan: binary nginx tidak terpasang di mesin tempat chapter ini ditulis (perintah command -v nginx tidak menghasilkan apa-apa), sehingga seluruh konfigurasi disajikan sebagai blok bacaan. Untuk tetap teruji, logika pencarian berkas yang nginx lakukan kita tiru dalam PHP murni dan dieksekusi sungguhan.

Peran Nginx dalam Arsitektur Web #

Membagi tugas antara nginx dan PHP-FPM adalah penerapan prinsip satu komponen satu tanggung jawab. Nginx menguasai semua urusan protokol HTTP di luar interpreter, sementara PHP-FPM fokus mengeksekusi kode. Pembagian kerja tersebut kira-kira sebagai berikut:

TugasDitangani oleh
Menerima HTTPS/TLS, kompresi gzip, buffering responsnginx
Menyajikan CSS, JavaScript, dan gambar langsung dari disknginx
Pembatasan laju (rate limiting) dan penyaringan IPnginx
Mengeksekusi kode PHPPHP-FPM
Batas memori, batas worker, dan timeout per poolPHP-FPM

Pola ini juga menjaga keamanan: berkas sumber PHP tidak perlu diproses oleh web server sama sekali, dan hanya jalur location tertentu yang diteruskan ke interpreter. Konsekuensi yang perlu diingat, nginx dan FPM kini adalah dua layanan yang sama-sama harus hidup — saat debugging “halaman kosong”, periksa keduanya, bukan hanya salah satu.

Pola Reverse Proxy #

Dalam pola ini, browser hanya berbicara dengan nginx; keberadaan PHP-FPM tidak terlihat dari luar. Nginx memutuskan per permintaan: berkas statis dijawab langsung dari disk, sedangkan yang memerlukan PHP diteruskan lewat protokol FastCGI.

Diagram di atas menunjukkan dua jalur respons: yang atas kembali ke browser seketika (berkas statis), sedangkan yang bawah melewati worker FPM dan database sebelum hasilnya dialirkan kembali. Karena jalur statis tidak menyentuh PHP sama sekali, aplikasi Anda otomatis dapat menyajikan aset dengan latensi serendah mungkin — asal konfigurasinya mengarahkannya ke sana.

Server Block Minimal untuk PHP #

Berikut konfigurasi server nginx paling ringkas yang mampu menjalankan aplikasi PHP modern:

nginx
server {
    listen 80;
    server_name toko.example.com;
    root /var/www/toko/public;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.4-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

Mari kita baca baris demi baris:

  1. listen 80; dan server_name: nginx menerima koneksi di port 80 dan mencocokkan domain permintaan dengan server block ini.
  2. root dan index: direktori dasar aplikasi (folder public yang hanya berisi titik masuk dan aset) dan berkas yang dicoba saat pengguna membuka direktori.
  3. location / dengan try_files: untuk semua URI, coba berkasnya ($uri), lalu direktorinya ($uri/), dan bila keduanya tidak ada, serahkan ke index.php beserta query string-nya — pola front controller yang dibedah di bagian khusus di bawah.
  4. location ~ \.php$: regex yang cocok untuk URI berakhiran .php; hanya permintaan inilah yang diteruskan ke FPM.
  5. include fastcgi_params;: memuat kumpulan parameter FastCGI standar (REQUEST_METHOD, CONTENT_TYPE, dan lainnya) yang dibutuhkan $_SERVER terisi benar.
  6. fastcgi_param SCRIPT_FILENAME ...: memberi tahu FPM berkas PHP mana yang harus dieksekusi — gabungan direktori root dengan skrip yang diminta.

Location Regex dan Permintaan yang Sampai ke PHP #

Ekspresi \.php$ menentukan permintaan mana yang diteruskan ke FPM — akhiran $ berarti pencocokan di ujung URI. Perilaku regex ini mudah salah baca, jadi kita uji logikanya dengan preg_match() yang memakai pola setara:

php
<?php
$permintaan = [
    '/index.php',
    '/produk/123',
    '/css/app.css',
    '/uploads/avatar.php.png',
    '/index.php/produk/123',
];

foreach ($permintaan as $uri) {
    $tujuan = preg_match('#\.php$#', $uri) === 1
        ? 'PHP-FPM (FastCGI)'
        : 'berkas statis / front controller';
    printf("%-26s => %s\n", $uri, $tujuan);
}

Output yang Dihasilkan:

text
/index.php                 => PHP-FPM (FastCGI)
/produk/123                => berkas statis / front controller
/css/app.css               => berkas statis / front controller
/uploads/avatar.php.png    => berkas statis / front controller
/index.php/produk/123      => berkas statis / front controller

Mari kita amati hasilnya:

  1. /index.php: berakhiran .php, cocok, diteruskan ke FPM — kasus normal.
  2. /uploads/avatar.php.png: tidak cocok karena URI berakhiran .png; nginx menyajikannya sebagai berkas statis. Nama berkas yang menyerupai PHP tidak otomatis dieksekusi.
  3. /index.php/produk/123: juga tidak cocok, karena URI berakhiran 123. URI berformat skrip.php/jalur/tambahan memakai PATH_INFO, dan pola sederhana \.php$ menutup jalur itu — biasanya justru diinginkan demi keamanan.

Bila aplikasi Anda memang membutuhkan format index.php/jalur (misalnya API tanpa rewrite), pola location harus memecah URI secara eksplisit:

nginx
location ~ [^/]\.php(/|$) {
    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_path_info;
    fastcgi_pass unix:/run/php/php8.4-fpm.sock;
}

Direktif fastcgi_split_path_info memisahkan bagian skrip dari bagian jalur tambahan, lalu PATH_INFO dikirim ke FPM sebagai parameter tersendiri. Pengaturan sisi PHP yang berpasangan dengan ini (seperti cgi.fix_pathinfo) dibahas pada chapter Production Configuration .

try_files untuk Front Controller #

Framework modern (Laravel, Symfony) memakai satu berkas index.php sebagai satu-satunya titik masuk; seluruh routing terjadi di dalam PHP. Direktif try_files $uri $uri/ /index.php?$query_string; mengimplementasikan perilaku itu: coba berkas apa adanya, coba index direktori, bila gagal serahkan URI lengkap ke front controller. Logika keputusannya dapat kita tiru dan uji dalam PHP:

php
<?php
function resolveTryFiles(string $uri, array $berkasAda): string
{
    $path = ltrim(strtok($uri, '?'), '/');

    if (in_array($path, $berkasAda, true)) {
        return "sajikan berkas /$path";
    }
    if (in_array(rtrim($path, '/') . '/index.html', $berkasAda, true)) {
        return 'sajikan index direktori /' . rtrim($path, '/');
    }
    return 'jalankan front controller /index.php';
}

$berkasAda = [
    'index.php',
    'css/app.css',
    'img/logo.svg',
    'produk/index.html',
];

foreach (['/css/app.css?v=3', '/img/logo.svg', '/produk/', '/produk/123'] as $uri) {
    printf("%-18s => %s\n", $uri, resolveTryFiles($uri, $berkasAda));
}

Output yang Dihasilkan:

text
/css/app.css?v=3   => sajikan berkas /css/app.css
/img/logo.svg      => sajikan berkas /img/logo.svg
/produk/           => sajikan index direktori /produk
/produk/123        => jalankan front controller /index.php

Mari kita bedah simulasi tersebut:

  1. Baris 4 (strtok + ltrim): query string dipangkas lebih dulu (?v=3 bukan bagian nama berkas) dan garis miring awal dibuang agar URI dapat dibandingkan dengan nama berkas relatif terhadap root — persis seperti yang nginx lakukan secara internal.
  2. Baris 6-8 (pemeriksaan $uri): URI yang persis berupa berkas yang ada — seperti css/app.css dan img/logo.svg — dijawab langsung tanpa menyentuh PHP.
  3. Baris 9-11 (pemeriksaan $uri/): URI direktori seperti /produk/ diselesaikan lewat index.html di dalamnya.
  4. Baris 12: sisanya, termasuk /produk/123 yang tidak ada di disk, jatuh ke front controller index.php — di sinilah router PHP mengambil alih.

Header Cache untuk Aset Statis #

Setelah front controller beres, aset statis adalah optimasi termurah yang bisa Anda lakukan. Satu blok location memberi tahu browser bahwa berkas gambar, CSS, dan font tidak akan berubah dan pantas disimpan lama:

nginx
location ~* \.(css|js|png|jpg|jpeg|svg|woff2)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
    access_log off;
}

Direktif expires 30d menghasilkan header Expires 30 hari ke depan, sementara Cache-Control: public, immutable membolehkan proxy publik menyimpan salinan dan memberi sinyal bahwa berkas tidak berubah — browser bahkan tidak perlu memvalidasi ulang dengan If-Modified-Since. Untuk berkas yang memang berubah antar rilis, pola umumnya menambahkan sidik jari pada nama berkas (misalnya app.a1b2c3.css) sehingga konten baru otomatis berarti URL baru. Baris access_log off menjaga log tetap ramping dari jutaan baris permintaan aset yang tidak informatif.

Header Keamanan di Tingkat Nginx #

Karena nginx adalah komponen pertama yang menyentuh setiap respons, header keamanan paling efisien dipasang di sini — sekali pasang, berlaku untuk seluruh aplikasi tanpa menyentuh kode PHP:

nginx
add_header X-Frame-Options           "SAMEORIGIN" always;
add_header X-Content-Type-Options    "nosniff" always;
add_header Referrer-Policy           "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000" always;

Makna tiap header secara singkat: X-Frame-Options: SAMEORIGIN melarang situs lain membingkai halaman Anda (mitigasi clickjacking), X-Content-Type-Options: nosniff mencegah browser menebak tipe konten yang berbeda dari deklarasinya, Referrer-Policy membatasi informasi URL yang ikut terbawa saat pengguna menautkan keluar, dan Strict-Transport-Security memerintahkan browser hanya berbicara HTTPS dengan domain Anda selama setahun ke depan. Kata kunci always memastikan header tetap terkirim pada respons error (4xx, 5xx), bukan hanya saat sukses — kelalaian umum yang membuat halaman error justru kehilangan perlindungan.

TLS Termination #

Konsep TLS termination berarti urusan enkripsi berakhir di nginx: browser berbicara HTTPS dengan nginx, sementara nginx berbicara FastCGI (yang tidak dienkripsi) dengan FPM di mesin yang sama. Sertifikat cukup dipasang di satu tempat, dan sertifikat yang kedaluwarsa cukup diperbarui di satu tempat pula:

nginx
server {
    listen 443 ssl;
    server_name toko.example.com;

    ssl_certificate     /etc/letsencrypt/live/toko.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/toko.example.com/privkey.pem;

    # ... lokasi location seperti contoh di atas ...
}

Praktik modern menggandengkan ini dengan Certbot (Let’s Encrypt) yang memperbarui sertifikat otomatis setiap ~60 hari, lalu memuat ulang nginx tanpa gangguan. Koneksi antara nginx dan FPM di mesin yang sama memang tidak terenkripsi, dan itu wajar — lalu lintasnya tidak pernah meninggalkan mesin; enkripsi transport internal baru diperlukan bila FPM berjalan di mesin terpisah.

Soket Unix vs TCP Loopback #

Contoh-contoh di atas memakai fastcgi_pass unix:/run/php/php8.4-fpm.sock;. Pilihan ini harus cocok dengan direktif listen milik pool FPM: soket Unix untuk satu mesin (lebih cepat, dijaga izin berkas), atau fastcgi_pass 127.0.0.1:9000; bila FPM berjalan di mesin atau container terpisah. Perbandingan lengkap beserta risiko port 9000 yang terbuka sudah kita bahas pada chapter PHP-FPM — cukup diingat aturan praktisnya: alamat di fastcgi_pass dan di listen harus menunjuk objek yang persis sama, jika tidak nginx menjawab 502 Bad Gateway tanpa pesan yang jelas.

Ringkasan #

Nginx menjadi pintu depan yang efisien: menyajikan statis langsung, meneruskan hanya URI berakhiran .php ke FPM dengan SCRIPT_FILENAME yang benar, menjatuhkan sisanya ke front controller lewat try_files, dan memusatkan urusan cache, header keamanan, serta TLS. Blok server di atas adalah kerangka yang bisa Anda kembangkan per aplikasi. Sebelum PHP-FPM dan pola reverse proxy menjadi arus utama, ada cara lain yang lebih tua dan masih sangat banyak dipakai — melekatkan PHP langsung ke dalam web server Apache, topik chapter Apache dan PHP berikutnya.

Referensi #