Nginx + PHP-FPM untuk Laravel: Server Block yang Benar
Di artikel pertama seri ini, server Anda sudah dibereskan: user deploy, login kunci SSH, firewall menyala, dan swap terpasang. Sekarang giliran bagian yang paling sering bikin frustrasi saat pertama deploy Laravel: membuka domain, lalu yang muncul malah 404, 502, atau — lebih menyebalkan — daftar berkas yang bisa diunduh. Hampir selalu, penyebabnya bukan kode Laravel Anda. Penyebabnya adalah server block Nginx yang mengarah ke tempat yang salah, atau PHP-FPM yang tidak terhubung dengan benar.
Artikel ini memasang Nginx dan PHP-FPM dengan cara yang selaras dengan dokumentasi deployment Laravel: root mengarah ke direktori public, semua permintaan yang bukan berkas statis diteruskan ke index.php, dan berkas tersembunyi seperti .env tidak bisa diakses dari luar. Di ujungnya, HTTPS dari Let's Encrypt ikut menyala. Artikel ini mengasumsikan fondasi artikel pertama sudah beres dan domain Anda sudah mengarah ke IP server — tanpa DNS yang mengarah benar, Certbot tidak bisa memverifikasi kepemilikan domain di langkah terakhir.
Pilih versi PHP dulu, sebelum menginstal apa pun
Dokumentasi deployment Laravel mensyaratkan PHP minimal 8.2 dan ekstensi Ctype, cURL, DOM, Fileinfo, Filter, Hash, Mbstring, OpenSSL, PCRE, PDO, Session, Tokenizer, dan XML. Di Ubuntu 24.04 LTS, repositori bawaan menyediakan PHP 8.3 — melewati lantai itu tanpa perlu repositori pihak ketiga. Itu alasan artikel ini memakai php8.3 di semua nama paket dan jalur. Kalau Anda memakai Ubuntu versi lain, ganti angka 8.3 dengan versi PHP yang tersedia di sana, tapi jangan turun di bawah 8.2: Composer akan menolak menginstal Laravel yang mensyaratkan versi lebih baru, dan galatnya sering baru muncul di tengah proses deploy.
Sebelum instalasi, perbarui paket dulu — sama seperti artikel pertama. Selain menjaga daftar paket tetap segar, langkah ini memastikan Anda mendapat paket PHP 8.3 yang sudah memuat perbaikan keamanan USN-8948-1 (Oktober 2026) untuk kerentanan PHP2Shell, bukan versi yang masih rentan:
apt update && apt upgrade -y
Langkah 1: Instal Nginx, PHP-FPM, dan ekstensi Laravel
Satu perintah memasang semuanya. Paket php8.3-fpm adalah proses PHP yang akan menerima permintaan dari Nginx; php8.3-cli adalah PHP baris perintah untuk Artisan dan Composer nanti; sisanya adalah ekstensi yang dibutuhkan Laravel plus beberapa yang hampir selalu dipakai aplikasi nyata (ZIP untuk manipulasi arsip, BCMath untuk perhitungan presisi, Intl untuk pemformatan lokal, dan driver database):
apt install -y nginx php8.3-fpm php8.3-cli php8.3-common php8.3-mbstring php8.3-xml php8.3-curl php8.3-zip php8.3-bcmath php8.3-intl php8.3-mysql php8.3-sqlite3
Paket php8.3-mysql dan php8.3-sqlite3 boleh dihapus salah satunya kalau Anda sudah yakin hanya memakai satu jenis database — tapi memasang keduanya di awal lebih murah daripada mengulang deploy karena driver yang hilang. Verifikasi hasil instalasi:
php -v php -m | grep -E 'mbstring|curl|xml|zip|bcmath|intl' systemctl status php8.3-fpm --no-pager
Hasil yang diharapkan: php -v menampilkan PHP 8.3.x, daftar ekstensi memuat nama-nama di atas, dan status FPM active (running). Kalau FPM tidak jalan, baca dulu journalctl -u php8.3-fpm --no-pager -n 50 sebelum mengubah konfigurasi apa pun — galat di awal biasanya soal socket atau permission, bukan soal aplikasi.
Periksa juga di mana socket FPM mendengarkan, karena jalur ini harus sama persis dengan yang Anda tulis di server block nanti:
ls /run/php/
Hasil yang diharapkan: ada berkas bernama php8.3-fpm.sock. Di Ubuntu, jalur kanonisnya /run/php/php8.3-fpm.sock (dokumentasi Laravel mencontohkan /var/run/php/php8.2-fpm.sock — /var/run adalah symlink ke /run, jadi keduanya menunjuk tempat yang sama, tapi menulis jalur /run/php/ secara eksplisit membuat Anda tidak perlu menebak).
Langkah 2: Atur FPM pool untuk satu aplikasi
Konfigurasi pool FPM tinggal di /etc/php/8.3/fpm/pool.d/www.conf. Untuk satu aplikasi di satu server, pool bawaan www sudah cukup — yang perlu Anda selaraskan adalah tiga hal: siapa yang menjalankan proses, socket apa yang dipakai, dan berapa banyak worker yang diizinkan. Buka berkasnya:
nano /etc/php/8.3/fpm/pool.d/www.conf
Pastikan baris-baris berikut bernilai seperti ini (sebagian sudah menjadi bawaan Ubuntu, periksa saja jangan sampai berbeda):
user = www-data group = www-data listen = /run/php/php8.3-fpm.sock listen.owner = www-data listen.group = www-data listen.mode = 0660 pm = dynamic pm.max_children = 20 pm.start_servers = 4 pm.min_spare_servers = 2 pm.max_spare_servers = 6
Penjelasan singkat dari tiap pilihan: pm = dynamic membuat FPM menambah dan mengurangi worker sesuai beban, jadi server tidak menganggur dengan 20 worker saat sepi dan tidak kehabisan worker saat ramai. Angka pm.max_children = 20 adalah titik awal yang waras untuk VPS 4 GB — tiap worker PHP Laravel biasanya memakan 30–60 MB, jadi 20 worker berarti sekitar 0,6–1,2 GB pada beban puncak, masih menyisakan ruang untuk Nginx, database, dan queue worker. Kalau nanti worker sering mentok, naikkan angka ini sambil memantau RAM; kalau RAM yang mentok duluan, turunkan angkanya.
Satu keputusan yang sengaja tidak diubah di sini: proses FPM tetap berjalan sebagai www-data, bukan sebagai deploy. Alasannya menyangkut permission direktori aplikasi yang akan dibahas di artikel deploy berikutnya: berkas aplikasi dimiliki deploy:www-data, dan hanya direktori storage serta bootstrap/cache yang diberi izin tulis untuk grup. Dengan begitu, proses web bisa menulis ke tempat yang memang perlu ditulis, tapi tidak bisa mengubah kode aplikasi.
Setelah mengedit, uji dan muat ulang FPM:
php-fpm8.3 -t systemctl reload php8.3-fpm
Hasil yang diharapkan: uji konfigurasi menampilkan pesan sukses dan reload selesai tanpa galat. Perintah php-fpm8.3 -t di sini memakai biner khusus versi — jangan tertukar dengan php -t yang tidak ada.
Langkah 3: Tulis server block yang root-nya ke public
Ini inti artikelnya, dan ini juga tempat kesalahan paling mahal bersembunyi. Dokumentasi Laravel menegaskan: web server harus mengarah ke direktori public aplikasi, dan index.php di dalamnya adalah front controller untuk semua permintaan HTTP yang masuk. Jangan pernah mengarahkan root ke folder proyek, dan jangan pernah memindahkan index.php keluar dari public — keduanya membuka akses publik ke berkas sensitif seperti .env.
Buat berkas server block di /etc/nginx/sites-available/laravel.conf. Ganti aplikasi.example.com dengan domain Anda dan /var/www/aplikasi dengan jalur proyek Anda:
nano /etc/nginx/sites-available/laravel.conf
server {
listen 80;
listen [::]:80;
server_name aplikasi.example.com www.aplikasi.example.com;
root /var/www/aplikasi/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
client_max_body_size 32M;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location ~ /\.(?!well-known).* {
deny all;
}
}Mari bedah baris-baris yang paling menentukan. root /var/www/aplikasi/public adalah keputusan keamanannya: yang bisa diakses publik hanyalah isi public, bukan seluruh proyek. try_files $uri $uri/ /index.php?$query_string adalah keputusan routing-nya: Nginx melayani berkas statis langsung (CSS, gambar, JS hasil build), dan segala hal lain diteruskan ke index.php beserta query string-nya — persis seperti yang diharapkan router Laravel. Tanpa baris ini, semua rute selain halaman depan akan 404.
Di blok PHP, try_files $uri =404 adalah pengaman penting: berkas PHP yang tidak ada tidak boleh diteruskan ke FPM, karena itu membuka celah eksekusi berkas sembarangan. fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name memberi tahu FPM berkas mana yang harus dieksekusi, dengan jalur yang sudah di-resolve (ini penting kalau jalur proyek Anda nanti memakai symlink, seperti strategi deploy berbasis release di artikel kelima seri ini). Terakhir, blok location ~ /\.(?!well-known).* menolak semua permintaan ke berkas tersembunyi — .env, .git, .htaccess — kecuali /.well-known/, yang harus tetap bisa diakses karena Certbot memakainya untuk verifikasi domain di langkah terakhir.
Nilai client_max_body_size 32M mengatur batas ukuran upload di sisi Nginx. Angka ini harus selaras dengan pengaturan PHP di /etc/php/8.3/fpm/php.ini — buka berkas itu dan samakan:
upload_max_filesize = 32M post_max_size = 32M
Kalau keduanya tidak selaras, yang berlaku adalah nilai yang lebih kecil, dan pengguna akan mendapat galat 413 (di Nginx) atau upload yang diam-diam gagal (di PHP) tanpa pesan yang jelas. Setelah mengubah php.ini, muat ulang FPM lagi: systemctl reload php8.3-fpm.
Aktifkan situs dan uji konfigurasinya:
ln -s /etc/nginx/sites-available/laravel.conf /etc/nginx/sites-enabled/ rm /etc/nginx/sites-enabled/default nginx -t systemctl reload nginx
Menghapus default itu penting: kalau tidak, Nginx masih menjawab domain apa pun dengan halaman selamat datang bawaan, dan Anda akan kebingungan kenapa domain Anda menampilkan halaman yang salah padahal server block sudah benar. Hasil yang diharapkan dari nginx -t: syntax is ok dan test is successful.
Di titik ini, aplikasi Laravel Anda mungkin belum ada di /var/www/aplikasi — dan itu tidak apa-apa. Untuk membuktikan Nginx dan FPM sudah terhubung, buat dulu berkas sementara /var/www/aplikasi/public/index.php berisi <?php phpinfo(); dan buka domain Anda di browser. Kalau halaman info PHP muncul, jalurnya benar. Hapus berkas itu segera setelah pengujian — halaman phpinfo() membocorkan detail versi dan konfigurasi server ke siapa pun yang membukanya.
Langkah 4: Nyalakan HTTPS dengan Certbot
Setelah situs menjawab lewat HTTP dan DNS domain sudah mengarah ke server, pasang Certbot beserta plugin Nginx-nya:
apt install -y certbot python3-certbot-nginx certbot --version
Lalu minta sertifikatnya:
certbot --nginx -d aplikasi.example.com -d www.aplikasi.example.com
Certbot akan menanyakan alamat email (untuk pemberitahuan perpanjangan) dan apakah HTTP harus dialihkan ke HTTPS — pilih pengalihan (redirect) supaya semua trafik lama ikut terenkripsi. Setelah selesai, Certbot sudah mengubah server block Anda sendiri: menambahkan blok listen 443 ssl beserta jalur sertifikat di /etc/letsencrypt/live/aplikasi.example.com/. Jangan mengedit blok hasil Certbot itu secara manual kecuali Anda tahu persis yang diubah; kalau server block perlu diubah lagi, jalankan certbot --nginx ulang atau edit dengan hati-hati lalu nginx -t.
Buka port HTTPS di firewall — selama ini UFW hanya membuka SSH, dan Nginx yang terinstal belakangan belum pernah diizinkan:
ufw allow 'Nginx Full' ufw status verbose
Hasil yang diharapkan: aturan Nginx Full tercantum sebagai ALLOW, dan https://aplikasi.example.com terbuka dengan gembok valid. Terakhir, uji perpanjangan otomatisnya — sertifikat Let's Encrypt hanya berlaku 90 hari, dan Ubuntu menjalankan pemeriksaan perpanjangan lewat systemd timer, jadi Anda tidak perlu membuat cron sendiri:
certbot renew --dry-run systemctl is-active certbot.timer
Hasil yang diharapkan: simulasi perpanjangan selesai tanpa galat dan timer dilaporkan active. Kalau simulasi gagal, jangan menunggu 90 hari untuk sadar — periksa systemctl status certbot.timer dan log di /var/log/letsencrypt/ sekarang juga.
Kalau muncul galat, baca tabel ini dulu
| Gejala | Kemungkinan penyebab | Perintah cek |
|---|---|---|
| 502 Bad Gateway | FPM tidak jalan, atau socket di server block tidak cocok dengan socket FPM | systemctl status php8.3-fpm, ls /run/php/, tail -n 50 /var/log/nginx/error.log |
| 404 di semua rute kecuali halaman depan | try_files tidak meneruskan ke index.php, atau root bukan public | Periksa root dan blok location / di /etc/nginx/sites-available/laravel.conf |
| Berkas PHP terunduh, bukan dieksekusi | Blok location ~ \.php$ hilang atau FPM tidak terhubung | nginx -T | grep -A 8 'location ~' |
| 413 Request Entity Too Large | Upload melebihi client_max_body_size atau upload_max_filesize | Samakan keduanya di server block dan /etc/php/8.3/fpm/php.ini, lalu reload Nginx dan FPM |
| 403 Forbidden | Permission direktori tidak mengizinkan www-data membaca public | namei -l /var/www/aplikasi/public/index.php, pastikan grup dan izin baca beres |
| Halaman kosong / 500 setelah deploy | APP_DEBUG masih true di produksi, atau storage tidak bisa ditulis | tail -n 100 /var/www/aplikasi/storage/logs/laravel.log, periksa izin storage dan bootstrap/cache |
| Certbot gagal verifikasi | DNS belum mengarah ke server, atau port 80 tertutup firewall | dig +short aplikasi.example.com, ufw status, curl -I http://aplikasi.example.com |
Aturan umum saat mendiagnosis: log Nginx di /var/log/nginx/error.log menjawab pertanyaan "apakah permintaan sampai ke FPM dan kenapa ditolak", sementara log Laravel di storage/logs/laravel.log menjawab "apakah aplikasi sudah jalan tapi gagal di dalam". Mengecek keduanya sebelum mengubah konfigurasi menghemat satu putaran tebak-tebakan.
Tabel ringkasan
| Komponen | Keputusan | Berkas kunci |
|---|---|---|
| PHP | 8.3 dari repositori Ubuntu 24.04 (memenuhi syarat minimal Laravel 8.2) | php8.3-fpm, php8.3-cli, ekstensi mbstring/xml/curl/zip/bcmath/intl/mysql/sqlite3 |
| FPM pool | www, user www-data, socket /run/php/php8.3-fpm.sock, pm = dynamic, maks 20 worker | /etc/php/8.3/fpm/pool.d/www.conf |
| Server block | Root ke public, try_files ke index.php, blok berkas tersembunyi | /etc/nginx/sites-available/laravel.conf |
| Batas upload | 32M di Nginx dan PHP, selaras | Server block + /etc/php/8.3/fpm/php.ini |
| HTTPS | Certbot plugin Nginx, redirect HTTP ke HTTPS, perpanjangan via timer | /etc/letsencrypt/live/aplikasi.example.com/, certbot.timer |
| Firewall | Tambah Nginx Full setelah HTTPS menyala | ufw allow 'Nginx Full' |
Checklist akhir
php -vmenampilkan PHP 8.3 dan ekstensi Laravel terpasang.systemctl status php8.3-fpmaktif, socket/run/php/php8.3-fpm.sockada.nginx -tlolos, situs default sudah dihapus darisites-enabled.- Domain menjawab lewat HTTP, dan halaman
phpinfo()sementara sudah dihapus setelah pengujian. https://terbuka dengan sertifikat valid, HTTP dialihkan ke HTTPS.certbot renew --dry-runlolos dancertbot.timeraktif.ufw statusmenampilkan Nginx Full di samping OpenSSH.- Berkas
.envtidak bisa dibuka dari browser (uji:curl -I https://aplikasi.example.com/.envharus 403 atau 404, bukan 200).
Artikel berikutnya masuk ke proses deploy itu sendiri: mengambil kode lewat Git, memasang dependensi dengan Composer, menjalankan migrasi dengan aman, dan menautkan storage — semua di atas server block yang baru saja Anda susun ini.
Galat pertama apa yang Anda temui saat pertama kali memasang Nginx untuk Laravel — 502, 404 di semua rute, atau yang lain? Ceritakan di komentar beserta penyebab yang akhirnya Anda temukan, supaya pembaca lain tidak mengulang jalan yang sama.
Baca juga: fondasi server dari artikel sebelumnya di {{URL-ID-29}}, gambaran cara satu VPS melayani banyak layanan lewat Satu VPS Banyak Layanan: Nginx Reverse Proxy, dan pengantar Docker Compose untuk self-hosting di Docker Compose untuk Self-Hosting dari Nol.


Komentar
Posting Komentar