Linux : Praktikum Virtual Host Nginx dan Diagnosa Situs yang Salah Tampil

🎯 Tujuan

Setelah membaca artikel ini, kamu bisa membangun dua situs berbeda di satu Nginx, membuktikan situs mana yang benar-benar menjawab, dan mendiagnosis keluhan "yang muncul malah situs lain" tanpa menebak-nebak.

Ini praktikum pendamping Linux : Nginx Dasar dan Virtual Host. Artikel pagi menjelaskan tiga lapis keputusan; artikel ini memasang ketiganya, lalu merusaknya satu per satu supaya kamu kenal gejalanya di dunia nyata.

πŸ“ Konteks nyata

Bayangkan kamu menjadi resepsionis gedung. Ada tamu yang menyebut lantai 3, ada yang menyebut lantai 5, dan ada yang hanya diam lalu berjalan sendiri ke lift mana pun yang terbuka.

Pekerjaanmu bukan menebak maksud tamu, tapi memastikan setiap nama yang disebut benar-benar sampai ke lantai yang benar. Kalau salah lantai, belum tentu tamunya salah sebut, belum tentu pula gedungnya rusak.

  • πŸ”₯ Kasus nyata: Sebuah distributor alat kesehatan memindahkan tiga situs internal ke server baru. Semua domain sudah mengarah ke IP baru, sertifikat sudah dipasang, dan halaman utama tampil normal. Masalahnya, halaman katalog menampilkan daftar harga tahun lalu. Selama lima hari tim penjualan mengutip harga lama ke apotek, dan kerugiannya baru ketahuan saat ada pesanan yang ditolak. Penyebabnya satu baris: root di server block katalog masih menunjuk salinan folder lama yang ikut tersalin saat migrasi. Tidak ada error, tidak ada log merah, karena secara teknis Nginx memang menjawab dengan benar.

🧠 Konsep singkat

Nama domain hanya menyebut alamat gedung. Yang memilih pintu dan ruangan adalah konfigurasi Nginx. Karena itu, keluhan "salah situs" hampir selalu masalah pemilihan, bukan masalah DNS.

DNS menjawab pertanyaan "server mana". Nginx menjawab pertanyaan "situs mana". Dua pertanyaan berbeda, dua alat periksa berbeda.

πŸ”Ή Lima lapis bukti

Jangan langsung mengubah isi halaman. Buktikan dulu lapis mana yang gagal, dari yang paling bawah.

Lapis Pertanyaan Alat bukti
Apakah ada proses yang mendengarkan? | systemctl is-active, ss -lntp
Apakah blok kita benar-benar dibaca? | nginx -t, nginx -T
Blok mana yang menjawab permintaan ini? | header Host, $server_name di log
File mana yang diambil dari disk? | $document_root, isi error.log
Apakah yang tampil memang versi hari ini? | curl, sha256sum, stat

Gejala tiap lapis sengaja berbeda. Kalau lapis 1 gagal, browser bilang koneksi ditolak. Kalau lapis 2 gagal, kamu bisa melihat situs default atau perubahan yang tidak pernah aktif. Kalau lapis 3 gagal, situs tetangga yang menjawab. Kalau lapis 4 gagal, muncul 404 atau 403. Kalau lapis 5 gagal, semuanya tampak sehat tetapi isinya salah.

πŸ”Ή Aturan berhenti di lapis pertama yang gagal

Ini aturan paling menghemat waktu di seluruh artikel: berhenti di lapis pertama yang gagal.

Kalau ss -lntp tidak menunjukkan port yang kamu tuju, jangan mengedit server_name. Kalau nginx -T tidak memuat blokmu, jangan menyalahkan file HTML. Kalau nama sudah benar, jangan menyentuh DNS.

Menyentuh lapis yang sehat justru menambah variabel baru, dan kamu kehilangan pembanding untuk tahu apa yang berubah.

πŸ”Ή Reload bukan restart, dan reload bisa gagal diam-diam

Dokumentasi resmi Nginx menjelaskan urutan saat menerima sinyal HUP (yang dikirim oleh reload): master memeriksa sintaks, mencoba memakai konfigurasi baru dengan membuka file log dan socket listen, lalu menjalankan worker baru.

Kalau langkah itu gagal, master membatalkan perubahan dan terus bekerja dengan konfigurasi lama. Artinya situs tetap hidup, tetapi konfigurasi barumu tidak pernah dipakai.

  • ⚠️ Hati-hati: Skrip otomatis yang menjalankan systemctl reload nginx tanpa memeriksa kode keluar akan melaporkan "selesai" padahal konfigurasi ditolak. Selalu jalankan nginx -t lebih dulu, dan periksa kode keluarnya.

Beda dengan restart: restart menghentikan proses lalu menyalakan ulang, sehingga koneksi yang sedang berjalan terputus. Untuk perubahan konfigurasi, reload adalah pilihan yang benar.

πŸ”Ή Dua variabel yang membongkar kebohongan

Saat membaca access log, dua variabel ini menjawab pertanyaan berbeda:

  • $host β€” nama yang diminta klien, diambil dari request line atau header Host.
  • $server_name β€” nama server block yang benar-benar menjawab permintaan itu.

Kalau keduanya berbeda, pemilihan nama sedang bermasalah. Kalau sama, lapis 3 sehat dan kamu boleh pindah ke lapis 4.

Tambahkan $document_root untuk melihat folder yang dipakai saat melayani permintaan itu. Inilah cara paling cepat membuktikan tuduhan "root-nya salah" tanpa membuka editor.

  • πŸ“Š Angka penting: Batas unggahan bawaan Nginx adalah 1m (1 MB) lewat client_max_body_size. Berkas lebih besar ditolak dengan kode 413 sebelum aplikasimu sempat melihatnya.

🧩 Istilah penting

Istilah Arti sederhana
Server block Satu blok server { }, mewakili satu situs
sites-available Gudang konfigurasi situs di Debian/Ubuntu
sites-enabled Folder symlink yang benar-benar dibaca nginx.conf
Symlink Berkas pintasan yang menunjuk berkas lain
default_server Situs penampung bila tidak ada nama yang cocok
root Folder dasar yang ditambahi URI
alias Path pengganti untuk satu location
$document_root Folder yang dipakai Nginx untuk permintaan saat ini
stub_status Halaman kecil berisi hitungan koneksi dan permintaan
namei -l Menampilkan izin setiap komponen path dari root
444 Kode khusus Nginx: tutup koneksi tanpa menjawab

πŸ”§ Praktik: dua situs, satu penolak, lima bukti

Lab ini memakai port 8080 agar tidak bentrok dengan web server lain yang mungkin sudah memakai port 80. Semua perintah dijalankan di VM Debian 12 khusus latihan.

  • ⚠️ Hati-hati: Jangan mengulang langkah "uji rusak" di server produksi. Satu default_server ganda bisa membuat seluruh situs berhenti dimuat saat reload. Ambil snapshot VM dulu.

πŸ”Ή Langkah 0: siapkan bahan

sudo apt update
sudo apt install -y nginx curl
nginx -v
systemctl is-active nginx
ss -lntp | grep ':8080' || echo "port 8080 masih bebas"

Kalau port 8080 sudah dipakai proses lain, ganti angka port di seluruh lab ini dan catat pilihanmu. Konsistensi angka lebih penting daripada angka itu sendiri.

πŸ”Ή Langkah 1: potret keadaan awal

sudo nginx -T > /tmp/nginx-awal.txt 2>/dev/null
wc -l /tmp/nginx-awal.txt
grep -n "include" /etc/nginx/nginx.conf
nginx -V 2>&1 | tr ' ' '\n' | grep stub_status

nginx -T mencetak konfigurasi gabungan yang benar-benar dipakai, termasuk hasil semua include. Simpan potret ini; nanti kamu membandingkan sebelum dan sesudah.

Baris terakhir memeriksa apakah modul stub_status tersedia di paket Nginx yang kamu pasang. Modul ini tidak selalu dikompilasi; kalau kosong, lewati Langkah 9 tanpa rasa bersalah.

  • πŸ§ͺ Uji cepat: Kalau nginx -T sudah gagal di keadaan awal, jangan menambah konfigurasi apa pun. Perbaiki dulu, karena kamu butuh baseline yang bersih.

πŸ”Ή Langkah 2: siapkan dua web root dan satu folder aset

sudo mkdir -p /srv/www/wiki.lab /srv/www/ujian.lab /srv/aset-bersama
printf 'WIKI TKJ versi 1\n'   | sudo tee /srv/www/wiki.lab/index.html
printf 'UJIAN ONLINE versi 1\n' | sudo tee /srv/www/ujian.lab/index.html
printf 'logo-lab\n'           | sudo tee /srv/aset-bersama/logo.txt

sudo chown -R www-data:www-data /srv/www /srv/aset-bersama
sudo find /srv/www /srv/aset-bersama -type d -exec chmod 755 {} \;
sudo find /srv/www /srv/aset-bersama -type f -exec chmod 644 {} \;

Isi kedua situs sengaja dibuat berbeda. Kalau isinya sama, kamu tidak akan pernah bisa membuktikan situs mana yang menjawab, dan seluruh lab ini kehilangan gunanya.

Folder /srv/aset-bersama dipisah dari web root untuk membuktikan cara kerja alias: file di luar folder situs tetap bisa disajikan pada URL tertentu.

πŸ”Ή Langkah 3: buat format log pembukti

Buat berkas /etc/nginx/conf.d/00-log-lab.conf:

log_format lab '$remote_addr [$time_local] "$request" '
                'host=$host server=$server_name status=$status '
                'root=$document_root bytes=$body_bytes_sent rt=$request_time';

Di Debian, folder conf.d di-include di dalam konteks http, jadi direktif log_format sah di sini. Kalau kamu menaruhnya di dalam blok server, Nginx akan menolak dengan unknown directive atau not allowed here.

Format ini mencatat empat hal yang biasanya ditebak orang: nama yang diminta, blok yang menjawab, folder yang dipakai, dan lama pemrosesan.

πŸ”Ή Langkah 4: tulis dua server block

Simpan sebagai /etc/nginx/sites-available/wiki-lab.conf:

server {
    listen 8080;
    server_name wiki.lab;

    root /srv/www/wiki.lab;
    index index.html;

    add_header X-Situs "wiki" always;
    access_log /var/log/nginx/wiki.access.log lab;
    error_log  /var/log/nginx/wiki.error.log;
}

Lalu /etc/nginx/sites-available/ujian-lab.conf:

server {
    listen 8080;
    server_name ujian.lab;

    root /srv/www/ujian.lab;
    index index.html;

    location /aset/ {
        alias /srv/aset-bersama/;
    }

    add_header X-Situs "ujian" always;
    access_log /var/log/nginx/ujian.access.log lab;
    error_log  /var/log/nginx/ujian.error.log;
}

Header X-Situs adalah penanda buatan sendiri. Ia tidak wajib ada di dunia nyata, tetapi sangat membantu saat belajar: kamu bisa melihat blok mana yang menjawab hanya dari header balasan.

  • πŸ’‘ Tips: Parameter always membuat header tetap dikirim pada respons 404 atau 403. Tanpa itu, add_header hanya berlaku pada kode balasan tertentu, sehingga penandamu hilang justru saat kamu paling membutuhkannya.
sudo ln -s /etc/nginx/sites-available/wiki-lab.conf  /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/ujian-lab.conf /etc/nginx/sites-enabled/

sudo nginx -t
sudo systemctl reload nginx; echo "exit=$?"

Kalau nginx -t gagal, jangan lanjut. Kode keluar dari reload juga penting: nilai bukan nol berarti konfigurasi baru ditolak dan konfigurasi lama masih melayani.

πŸ”Ή Langkah 6: buktikan lapis nama

curl -s -H 'Host: wiki.lab'  http://127.0.0.1:8080/
curl -s -H 'Host: ujian.lab' http://127.0.0.1:8080/
curl -sI -H 'Host: ujian.lab' http://127.0.0.1:8080/ | head -5

sudo tail -n 2 /var/log/nginx/wiki.access.log
sudo tail -n 2 /var/log/nginx/ujian.access.log

Baris pertama harus mencetak WIKI TKJ versi 1, baris kedua UJIAN ONLINE versi 1. Header balasan harus memuat X-Situs: ujian.

Di log, perhatikan pasangan host= dan server=. Keduanya harus sama. Kalau host=ujian.lab tetapi server=wiki.lab, kamu sedang melihat bukti langsung bahwa pemilihan nama salah.

πŸ”Ή Langkah 7: pasang penolak sebagai default

Simpan sebagai /etc/nginx/sites-available/tolak-lab.conf:

server {
    listen 8080 default_server;
    server_name _;
    return 444;
}

Aktifkan dan uji:

sudo ln -s /etc/nginx/sites-available/tolak-lab.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
curl -sv -H 'Host: tidak-ada.lab' http://127.0.0.1:8080/ 2>&1 | tail -3

Hasil yang diharapkan: Empty reply from server. Koneksi berhasil dibuat, tetapi tidak ada jawaban HTTP sama sekali. Ini berbeda dari Connection refused yang berarti lapis 1 bermasalah.

Dua situs aslimu harus tetap normal setelah perubahan ini. Kalau ikut mati, berarti default_server salah tempel di port lain.

πŸ”Ή Langkah 8: buktikan lapis path dan isi

curl -s -H 'Host: ujian.lab' http://127.0.0.1:8080/aset/logo.txt
sha256sum /srv/aset-bersama/logo.txt
curl -s -H 'Host: ujian.lab' http://127.0.0.1:8080/aset/logo.txt | sha256sum

namei -l /srv/www/wiki.lab/index.html
stat -c '%U:%G %a %n' /srv/www/wiki.lab/index.html

Dua nilai sha256sum harus identik. Kalau berbeda, yang kamu baca bukan berkas yang kamu kira, dan itu bukti kuat ada masalah path atau cache.

namei -l menampilkan izin setiap komponen path dari / sampai berkas tujuan. Inilah cara menemukan penyebab 403 yang tidak terlihat dari ls -l di folder akhir: bisa jadi folder induknya yang tidak bisa ditembus.

Sekarang ubah isi halaman tanpa menyentuh konfigurasi:

printf 'UJIAN ONLINE versi 2\n' | sudo tee /srv/www/ujian.lab/index.html
curl -s -H 'Host: ujian.lab' http://127.0.0.1:8080/

Isi langsung berubah tanpa reload. Ini aturan praktisnya: berkas statis dibaca ulang setiap permintaan, sedangkan konfigurasi hanya dibaca saat start atau reload.

πŸ”Ή Langkah 9: opsional, bukti dari sisi server

Kalau modul tersedia, tambahkan di dalam blok wiki.lab:

location = /basic_status {
    stub_status;
    allow 127.0.0.1;
    deny all;
}

Uji dengan

curl -s -H 'Host: wiki.lab' http://127.0.0.1:8080/basic_status

. Kamu akan melihat hitungan koneksi aktif, jumlah permintaan yang diterima, dan jumlah yang sedang dibaca atau ditulis.

  • πŸ” Hati-hati: stub_status membocorkan jumlah trafik ke siapa pun yang bisa membukanya. Batasi dengan allow/deny seperti contoh, atau jangan pasang sama sekali di server produksi.

πŸ”Ή Langkah 10: bersih-bersih

sudo rm -f /etc/nginx/sites-enabled/wiki-lab.conf
sudo rm -f /etc/nginx/sites-enabled/ujian-lab.conf
sudo rm -f /etc/nginx/sites-enabled/tolak-lab.conf
sudo nginx -t && sudo systemctl reload nginx

sudo rm -rf /srv/www/wiki.lab /srv/www/ujian.lab /srv/aset-bersama

Konfigurasi di sites-available sengaja dibiarkan supaya bisa dipakai ulang. Yang dihapus hanya symlink dan data latihan.

🏭 Dipakai di dunia kerja

  • Sekolah: satu server untuk wiki, aplikasi ujian, dan profil sekolah. Virtual host memisahkan ketiganya tanpa tiga IP publik, dan guru bisa memeriksa situs mana yang menjawab lewat log saat ujian tersendat.
  • Kantor dan UMKM: situs katalog, aplikasi absensi, dan halaman status berbagi satu VM. Kesalahan root seperti kasus distributor di atas adalah kegagalan yang paling sering lolos karena tidak memunculkan error.
  • Hosting bersama: ribuan domain di satu IP. Di skala ini, format log dengan $host dan $server_name bukan kemewahan, tetapi satu-satunya cara membuktikan pelanggan mana yang dilayani.
  • ISP dan warnet: portal login, billing, dan halaman status sering satu server. Salah pilih default_server berarti pelanggan melihat halaman yang bukan miliknya.
  • Industri dan e-commerce: dua server block dipakai untuk mengarahkan sebagian trafik ke versi baru sebelum rilis penuh, lalu dibalikkan cepat bila ada masalah.
  • Operasional DevOps: konfigurasi situs disimpan di git, diuji nginx -t di pipeline, lalu di-reload. Pipeline yang tidak memeriksa kode keluar akan meloloskan konfigurasi rusak tanpa ketahuan.

βœ… Verifikasi

Perintah Hasil yang diharapkan
systemctl is-active nginx active
ss -lntp | grep 8080 ada proses nginx mendengarkan port 8080
sudo nginx -t syntax is ok dan test is successful
sudo nginx -T | grep -c "server_name wiki.lab" minimal 1, bukan 0
curl -H 'Host: ujian.lab' UJIAN ONLINE versi 2
header X-Situs wiki untuk wiki, ujian untuk ujian
log: host= vs server= nilainya sama pada setiap baris
curl -s ... | sha256sum sama dengan hash berkas di disk
namei -l pada berkas web semua komponen bisa ditembus www-data
curl -H 'Host: tidak-ada.lab' Empty reply from server, bukan situs lain

⚠️ Kesalahan umum & troubleshooting

Sekarang bagian yang paling sering menyelamatkan waktu: delapan uji rusak terkendali. Lakukan satu per satu, catat gejalanya, lalu pulihkan sebelum lanjut ke uji berikutnya.

  • A β€” symlink dicabut. Ubah: rm /etc/nginx/sites-enabled/wiki-lab.conf lalu reload. Status: situs wiki menjawab dengan situs lain atau penolak. Bukti: nginx -T | grep -c "server_name wiki.lab" bernilai 0. Artinya: lapis 2 gagal, file konfigurasi masih ada tetapi tidak dibaca. Pulihkan dengan membuat symlink lagi.
  • B β€” symlink menunjuk berkas yang sudah hilang. Ubah: hapus berkas di sites-available sambil symlink-nya tetap ada. Status: nginx -t gagal dengan open() ".../sites-enabled/wiki-lab.conf" failed (2: No such file or directory). Bukti: situs lama tetap 200 karena konfigurasi lama masih dipakai. Artinya: reload yang gagal tidak mematikan apa pun, tetapi juga tidak menerapkan apa pun.
  • C β€” dua default_server di port yang sama. Ubah: tambahkan default_server pada blok ujian. Status: nginx -t gagal dengan duplicate default server for 0.0.0.0:8080. Artinya: konfigurasi batal dimuat seluruhnya, bukan hanya baris itu yang diabaikan.
  • D β€” server_name salah ketik. Ubah: server_name ujian.lab menjadi ujian.labs. Status: permintaan ke ujian.lab jatuh ke penolak, curl melaporkan Empty reply from server. Bukti: log ujian tidak bertambah sama sekali. Artinya: lapis 3 gagal; DNS dan sertifikat tidak ada urusan di sini.
  • E β€” alias tanpa garis miring penutup. Ubah: alias /srv/aset-bersama; tanpa / di akhir. Status: 404 untuk /aset/logo.txt. Bukti: error.log menyebut path yang dikonstruksi, misalnya /srv/aset-bersamalogo.txt. Artinya: alias mengganti teks, bukan menempel path, sehingga garis miring menentukan hasil.
  • F β€” izin berkas dicabut. Ubah: chmod 600 dan chown root:root pada index.html. Status: 403 Forbidden. Bukti: error.log memuat Permission denied (13). Artinya: berkas ada tetapi tidak bisa dibaca worker. Bandingkan dengan 404 yang biasanya disertai No such file or directory (2).
  • G β€” folder tanpa berkas index. Ubah: akses /kosong/ pada folder yang ada tetapi tanpa index.html, dan autoindex mati. Status: 403 dengan pesan directory index of "/srv/www/ujian.lab/kosong/" is forbidden. Artinya: ini perilaku benar, bukan kerusakan. Solusinya menambah berkas index atau mengaktifkan autoindex dengan sadar risikonya.
  • H β€” unggahan melebihi batas. Ubah: kirim badan permintaan 2 MB ke situs lab. Status: 413 Request Entity Too Large. Bukti: error.log memuat client intended to send too large body. Artinya: permintaan ditolak Nginx sebelum sampai ke aplikasi. Solusinya client_max_body_size 8m; pada blok situs yang memang menerima unggahan.
Kode Arti praktis Yang diperiksa dulu
Berkas ada, akses ditolak | izin dan namei -l
Path yang diminta tidak ketemu | root, alias, garis miring
Badan permintaan terlalu besar | client_max_body_size
Ditutup tanpa jawaban | nama tidak cocok, ini penolak sengaja
Upstream tidak menjawab | aplikasi di belakang, bukan lapis nama
Pesan atau gejala Arti Tindakan
bind() ... failed (98: Address already in use) port dipakai proses lain ss -lntp, ganti port atau hentikan proses
duplicate default server dua penampung di satu port hapus satu default_server
conflicting server name, ignored nama ditulis di dua blok hapus duplikat, yang pertama menang
unknown directive "log_format" format ditulis di konteks salah pindahkan ke conf.d atau dalam http
open() ... failed (2) symlink menunjuk berkas hilang buat ulang symlink atau hapus
403 dengan Permission denied (13) izin atau pemilik salah chown www-data, mode 644/755
403 directory index ... is forbidden tidak ada index, autoindex mati tambah index.html
404 tetapi berkas ada root/alias salah baca $document_root di log
Header X-Situs hilang add_header tidak diwarisi bila location punya add_header sendiri pakai always, periksa level
Reload sukses, situs tak berubah berkas tidak ter-include nginx -T | grep nama situs
Situs tampil isi lama root menunjuk salinan lama bandingkan hash berkas dan yang disajikan

πŸ” Catatan keamanan & etika

  • Jangan menaruh web root di dalam /etc/nginx. Konfigurasi dan isi situs harus dipisah agar tidak ada berkas konfigurasi yang bisa diunduh.
  • Jangan chmod 777. Cukup 755 untuk folder dan 644 untuk berkas, dengan pemilik www-data. Izin longgar adalah pintu masuk paling murah bagi penyerang.
  • Jangan simpan .git, .env, dump database, atau arsip cadangan di web root. Nginx menyajikan berkas apa adanya tanpa bertanya apakah itu rahasia.
  • Matikan autoindex kecuali untuk folder yang memang publik. Daftar berkas yang terbuka membocorkan struktur internal dan nama berkas sensitif.
  • Perlakukan nginx -T sebagai dokumen internal. Isinya path server, nama host, dan alamat upstream. Jangan menempel mentah-mentah ke grup chat.
  • Log akses memuat alamat IP dan URL yang dibuka pengguna. Itu data pribadi: batasi siapa yang bisa membaca, dan jangan mengirimkannya ke pihak lain tanpa alasan jelas.
  • Uji konfigurasi di lab, bukan di produksi. Satu default_server ganda bisa membuat seluruh situs berhenti dimuat saat reload berikutnya.
  • Etika paling dasar: jangan menguji situs milik orang lain, termasuk memakai header Host untuk melihat situs tetangga di server bersama.

πŸ“Œ Kesimpulan

Situs yang salah tampil bukan bukti DNS rusak. DNS hanya menyebut alamat server; Nginx yang memilih situs. Karena itu periksa berurutan: layanan, konfigurasi yang benar-benar dibaca, nama yang menjawab, path yang diambil, lalu isi yang disajikan.

Reload yang gagal tidak mematikan situs, tetapi juga tidak menerapkan perubahan. Itu sebabnya pemeriksaan kode keluar dan nginx -T bukan formalitas, melainkan satu-satunya cara tahu konfigurasi mana yang sedang bekerja.

Langkah berikutnya: tambahkan HTTPS pada salah satu situs, lalu pasang Nginx sebagai reverse proxy di depan aplikasi. Lapis keempat akan muncul, yaitu upstream, dan cara berpikirnya tetap sama: buktikan satu lapis setiap kali.

πŸ”— Materi terkait

πŸ“š Referensi