Variabel Bawaan

Variabel Bawaan #

Web server Nginx tidak memproses request secara statik dan kaku. Di balik kap mesinnya, Nginx menyediakan mesin evaluasi variabel dinamis yang sangat kuat. Variabel di Nginx tidak seperti variabel pada bahasa pemrograman imperatif tradisional yang nilainya dialokasikan secara statis di memori sejak awal. Di Nginx, variabel dialokasikan per request di dalam memory pool, dan nilainya dievaluasi secara dinamis (on-the-fly) hanya ketika ada modul atau directive yang memanggilnya.

Memahami variabel bawaan (embedded variables) Nginx akan membuat berkas konfigurasi kita jauh lebih cerdas, dinamis, dan adatip terhadap perilaku pengguna. Artikel ini akan menyajikan klasifikasi lengkap variabel bawaan Nginx, membedah perbedaan variabel yang membingungkan (seperti $host vs $http_host), menjelaskan implementasi aman modul map berbasis lazy evaluation, serta membongkar mitos dan fakta di balik peringatan terkenal “If is Evil” di lingkungan Nginx.

Kategori Lengkap Variabel Bawaan Nginx #

Nginx menyediakan ratusan variabel bawaan yang diisi secara otomatis dari data request, status koneksi TCP, enkripsi TLS, hingga kinerja upstream backend. Berikut adalah referensi lengkap variabel yang paling sering digunakan di lingkungan produksi:

1. Variabel Request (HTTP Headers & Payload) #

Variabel ini mengekstrak informasi langsung dari paket data HTTP request yang dikirimkan oleh browser klien:

  • $uri
    • Path URL saat ini yang telah dinormalisasi (didekode dari URL-encode, menghilangkan query string, dan memproses folder khusus seperti ../ menjadi path bersih). Nilai ini dapat berubah di tengah pemrosesan request jika kita melakukan internal redirect (misalnya menggunakan try_files atau rewrite).
  • $request_uri
    • URI request asli dan mentah persis seperti yang dikirimkan oleh browser klien, termasuk query string. Nilai ini bersifat konstan (read-only) dan tidak pernah berubah di sepanjang daur hidup request.
    • Contoh: Jika klien mengakses /search?q=nginx, maka $uri adalah /search, sedangkan $request_uri is /search?q=nginx.
  • $args
    • Seluruh query string setelah tanda tanya (e.g., q=nginx&page=2).
  • $arg_NAME
    • Mengambil nilai dari parameter query string tertentu berdasarkan namanya.
    • Contoh: Variabel $arg_q akan mengambil nilai nginx dari query string ?q=nginx.
  • $request_method
    • Metode HTTP yang digunakan (e.g., GET, POST, PUT, DELETE).
  • $http_NAME
    • Mengambil nilai dari header HTTP request apapun dengan mengubah nama header menjadi huruf kecil dan mengganti tanda hubung (-) dengan garis bawah (_).
    • Contoh: $http_user_agent (User-Agent header), $http_cookie (Cookie header), $http_x_forwarded_for (IP asal di balik proxy).

2. Variabel Koneksi dan Server Jaringan #

Mengambil parameter socket TCP dan status koneksi fisik jaringan:

  • $remote_addr
    • Alamat IP klien yang terhubung langsung ke Nginx. Jika server kita berada di balik Load Balancer (seperti Cloudflare atau AWS ALB), variabel ini akan berisi IP Load Balancer tersebut, bukan IP asli pengunjung.
  • $binary_remote_addr
    • Alamat IP klien dalam format biner (4 byte untuk IPv4, 16 byte untuk IPv6). Variabel ini sangat hemat memori dan digunakan sebagai kunci wajib pada pembatasan rate limit (limit_req_zone).
  • $scheme
    • Protokol yang digunakan oleh klien, mengembalikan nilai "http" atau "https".
  • $server_port
    • Port server yang menerima request (biasanya 80 atau 443).

3. Variabel SSL/TLS (Enkripsi) #

Hanya tersedia jika koneksi klien terjalin menggunakan protokol HTTPS:

  • $ssl_protocol
    • Versi protokol TLS yang disepakati (e.g., TLSv1.2, TLSv1.3).
  • $ssl_cipher
    • Algoritma cipher enkripsi yang digunakan (e.g., ECDHE-RSA-AES128-GCM-SHA256).

4. Variabel Upstream (Backend) #

Tersedia setelah Nginx meneruskan request ke server backend menggunakan proxy_pass atau fastcgi_pass:

  • $upstream_addr
    • IP address dan port server backend yang memproses request (e.g., 127.0.0.1:8080).
  • $upstream_status
    • HTTP status code yang dikembalikan oleh backend (e.g., 200, 500).
  • $upstream_response_time
    • Waktu yang dihabiskan oleh backend untuk memproses request dalam satuan detik (presisi milidetik).
  • $upstream_cache_status
    • Status caching Nginx untuk request tersebut (HIT, MISS, BYPASS, EXPIRED).

5. Variabel Waktu dan Durasi #

  • $time_iso8601
    • Waktu lokal saat request diterima dalam format ISO 8601 (e.g., 2026-01-15T10:30:45+07:00). Sangat direkomendasikan untuk format log modern agar mudah dibaca oleh log parser (seperti ELK Stack).
  • $request_time
    • Total waktu yang dihabiskan Nginx untuk melayani request (dalam detik dengan presisi milidetik), dihitung sejak byte pertama dari klien diterima hingga byte terakhir dikirimkan dan koneksi ditutup.

Bedah Kasus: $host vs $http_host #

Salah satu kebingungan terbesar saat mengonfigurasi reverse proxy adalah memilih antara variabel $host dan $http_host untuk diteruskan ke backend via directive proxy_set_header Host. Perbedaan kecil ini memiliki dampak fungsional dan keamanan yang besar:

1. Variabel $http_host (Mentah & Kaku) #

Variabel ini mengambil nilai mentah dari header Host yang dikirimkan oleh browser klien tanpa modifikasi apapun.

  • Perilaku: Jika browser mengirimkan request ke port non-standar (misalnya example.com:8080), variabel $http_host akan bernilai example.com:8080. Jika header Host kosong atau tidak dikirimkan oleh klien, variabel ini akan bernilai kosong.

2. Variabel $host (Normalisasi & Aman) #

Variabel ini didesain agar lebih tangguh dan aman. Nginx menghitung nilai $host dengan urutan fallback berikut:

  1. Jika ada header Host dari klien, Nginx menggunakannya setelah membuang informasi port (misalnya example.com:8080 dinormalisasi menjadi example.com) dan mengubahnya ke lowercase.
  2. Jika header Host tidak ada atau kosong, Nginx menggunakan nama server block yang cocok (server_name directive).
  3. Jika tidak ada server block yang cocok, Nginx menggunakan alamat IP server yang menerima koneksi.

Tabel Komparasi Kasus Input: #

Karakteristik Request MasukNilai $http_hostNilai $host (Rekomendasi)
Request ke example.comexample.comexample.com
Request ke example.com:8080example.com:8080 (Memuat port)example.com (Port dibuang)
Header Host Kosong / Tidak Dikirim(Kosong)nama_server_block_kita (Fallback aman)
Serangan Host Header Injectiondomain_peretas.comnama_server_block_kita (Fallback aman jika di-filter)
  • Best Practice: Selalu gunakan $host di dalam proxy_set_header untuk reverse proxy. Ini mencegah backend kita kebingungan dengan informasi port non-standar dan melindungi aplikasi dari serangan Host Header Injection.
    proxy_set_header Host $host;
    

Daur Hidup Variabel Waktu: Request Time vs Upstream Time #

Untuk mempermudah debugging bottleneck performa di produksi, kita harus memahami bagaimana Nginx menghitung durasi waktu request menggunakan variabel $request_time dan $upstream_response_time.

Berikut adalah visualisasi diagram alur (sequence diagram) daur hidup pemrosesan request dan titik penghitungan variabel waktu tersebut:

sequenceDiagram
    autonumber
    actor Klien
    participant Nginx
    participant Backend as Upstream (Go/Node)

    Klien->>Nginx: TCP Handshake & Mulai Kirim Byte Request pertama
    Note over Nginx: Mulai penghitungan $request_time
    
    Klien->>Nginx: Selesai kirim Header & Body Request
    Nginx->>Backend: Buka TCP & Teruskan Request (proxy_pass)
    Note over Nginx: Mulai penghitungan $upstream_response_time
    
    Backend->>Nginx: Kirim Byte Respons pertama
    Backend->>Nginx: Selesai kirim seluruh Data Respons
    Note over Nginx: Selesai $upstream_response_time
    
    Nginx->>Klien: Kirim Data Respons ke klien (melalui bandwidth internet)
    Klien->>Nginx: Konfirmasi penerimaan byte terakhir & Tutup Koneksi TCP
    Note over Nginx: Selesai $request_time (Logged)

Dari diagram di atas, kita dapat menyimpulkan:

  • Jika koneksi internet klien sangat lambat (misalnya di jaringan seluler), nilai $request_time akan jauh lebih besar dibandingkan $upstream_response_time (karena langkah 1 ke 2 dan langkah 7 ke 8 membutuhkan waktu lama).
  • Jika backend kita lambat memproses database, nilai $upstream_response_time akan membengkak, yang secara otomatis menarik naik nilai $request_time.
  • Analisis perbandingan kedua variabel ini di access log sangat berguna untuk mengidentifikasi apakah kelambatan web disebabkan oleh jaringan klien atau bottleneck server database kita.

Logika Kondisional Aman dengan Modul map (Lazy Evaluation) #

Di dalam Nginx, membuat logika percabangan kondisional (seperti: “jika request bertipe POST, maka lakukan X; jika mobile browser, alihkan ke Y”) menggunakan blok if adalah hal yang berisiko tinggi (lihat pembahasan If is Evil di bawah). Solusi terbaik dan paling efisien yang disediakan Nginx adalah menggunakan modul map.

Sintaksis dan Cara Kerja map #

Blok map hanya boleh ditulis di http context (global), di luar server block. map mengambil satu variabel input, membandingkannya dengan pola pencocokan, dan mengembalikan nilai baru ke dalam variabel output.

http {
    # Sintaks: map variabel_input variabel_output { ... }
    map $request_method $cache_bypass {
        default  0;
        POST     1;
        PUT      1;
        DELETE   1;
    }

    server {
        location / {
            proxy_pass http://backend;
            proxy_cache my_cache;
            
            # Gunakan variabel hasil pemetaan
            proxy_cache_bypass $cache_bypass;
        }
    }
}

Keunggulan Luar Biasa Modul map: #

  1. Lazy Evaluation (Evaluasi Malas): Nginx tidak memproses aturan di dalam blok map saat request masuk pertama kali. Evaluasi hanya dilakukan ketika variabel output (dalam contoh: $cache_bypass) dipanggil untuk pertama kalinya dalam request tersebut. Jika variabel tidak pernah dipanggil, CPU tidak akan membuang siklus untuk memproses pemetaan tersebut.
  2. Optimasi Hash Table: Pola pencocokan di dalam map dikompilasi ke dalam tabel hash statis di memori pada saat startup. Proses pencocokan domain atau string berjalan dalam kompleksitas waktu $O(1)$ yang instan, jauh lebih cepat daripada mengevaluasi rangkaian blok if secara sekuensial.

Berikut adalah diagram visual yang menggambarkan mekanisme Lazy Evaluation pada modul map Nginx:

flowchart TD
    Init["Request Masuk ke Nginx"] --> Phase1["Fase Inisialisasi: <br> Nginx TIDAK memproses blok 'map'"]
    
    Phase1 --> Phase2["Fase Content: <br> Nginx mengeksekusi directive 'proxy_cache_bypass $cache_bypass'"]
    
    Phase2 --> CallVar{"Apakah variabel $cache_bypass <br> sudah memiliki nilai?"}
    
    CallVar -->|"Ya (Sudah dievaluasi)"| UseVal["Gunakan nilai yang ada di memori RAM"]
    CallVar -->|"Tidak (Evaluasi Pertama)"| EvalMap["1. Baca nilai input saat ini ($request_method) <br> 2. Cari kecocokan di tabel hash map <br> 3. Tulis hasil ke variabel output $cache_bypass"]
    
    EvalMap --> SaveVal["Simpan nilai ke cache request memori"]
    SaveVal --> UseVal
    
    UseVal --> Respond["Eksekusi tindakan cache bypass"]
    
    style Phase2 stroke:#f57c00,stroke-width:2px
    style EvalMap stroke:#388e3c,stroke-width:2px

Logging Kustom dan Distributed Tracing dengan Variabel #

Variabel Nginx sangat krusial dalam menyusun strategi pemantauan (monitoring) aplikasi modern. Di lingkungan produksi, log standar Nginx sering kali tidak cukup untuk melacak perjalanan request melewati puluhan kontainer mikroservis.

1. Melacak Transaksi dengan $request_id #

Nginx menyediakan variabel $request_id yang berisi 32 karakter heksadesimal unik yang dibuat secara acak untuk setiap request masuk. Variabel ini bertindak sebagai Correlation ID global.

Kita dapat mengalirkan $request_id ini ke aplikasi backend kita dan mengembalikannya ke browser klien untuk mempermudah pelacakan error logs:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://localhost:8080;
        
        # Kirim Correlation ID ke server backend
        proxy_set_header X-Request-ID $request_id;
        
        # Kembalikan Correlation ID ke browser klien (untuk lapor bug)
        add_header X-Request-ID $request_id;
    }
}

Jika pengguna melihat error di browser mereka, mereka cukup melaporkan header X-Request-ID tersebut ke tim IT. Tim IT dapat menyalin ID tersebut dan mencari catatan log yang sama persis di Elasticsearch, database backend, maupun log Nginx secara instan.

2. Format Log Akses Produksi Lengkap #

Berikut adalah contoh pembuatan format log kustom yang memuat analisis mendalam performa I/O backend:

http {
    # Mendefinisikan format log kustom bernama 'production_trace'
    log_format production_trace '$remote_addr - $remote_user [$time_iso8601] '
                                 '"$request" $status $body_bytes_sent '
                                 '"$http_referer" "$http_user_agent" '
                                 'request_id="$request_id" '
                                 'req_time=$request_time '
                                 'up_conn_time=$upstream_connect_time '
                                 'up_header_time=$upstream_header_time '
                                 'up_resp_time=$upstream_response_time '
                                 'cache_status=$upstream_cache_status';

    # Aktifkan log akses menggunakan format di atas
    access_log /var/log/nginx/access.log production_trace;
}
  • $upstream_connect_time: Waktu (detik) yang dibutuhkan Nginx untuk melakukan handshake TCP/TLS dengan server backend. Jika nilai ini bengkak, berarti ada bottleneck overload di server backend atau masalah jaringan lokal.
  • $upstream_header_time: Waktu yang dihabiskan untuk menunggu backend mengirimkan header HTTP respons pertama. Ini mencerminkan performa komputasi aplikasi backend kita (misal lama query database).

Mythbuster: Mengapa Directive “if” di Location Block Berbahaya? #

Di komunitas Nginx, terdapat peringatan keras yang berbunyi: “If is Evil” (If itu jahat). Administrator sistem sangat dilarang menggunakan directive if di dalam location block untuk sebagian besar skenario, karena perilakunya sering kali tidak intuitif dan dapat menyebabkan crash server atau celah keamanan.

Mengapa if Berbahaya? (Penjelasan Teknis) #

Nginx tidak memproses file konfigurasi secara prosedural layaknya bahasa pemrograman biasa. Nginx mengeksekusi konfigurasi melalui 11 fase pemrosesan yang kaku (request processing phases).

Directive if adalah bagian dari modul Rewrite. Oleh karena itu, ia dievaluasi sangat awal pada fase rewrite, bahkan sebelum Nginx menentukan modul konten mana (seperti static file handler atau proxy_pass) yang harus melayani request tersebut.

Ketika sebuah kondisi if terpenuhi, Nginx secara internal melakukan tindakan ekstrem:

  1. Nginx membuat sebuah anonymous location block tersembunyi di dalam memori RAM.
  2. Request dialihkan secara paksa ke dalam anonymous location tersebut.
  3. Anonymous location ini tidak mewarisi sebagian besar directive penting dari location luar (seperti config proxy headers, security filters, atau zone limits).

Contoh Bug Fatal if (Anti-Pattern): #

# ANTI-PATTERN: Menyebabkan error 404 atau request macet!
location / {
    proxy_pass http://backend;

    # Kita ingin menambahkan header khusus untuk request dari IP tertentu
    if ($remote_addr = 192.168.1.100) {
        add_header X-Special-Client "true";
    }
}
  • Hasil: Ketika klien dengan IP 192.168.1.100 mengakses website, Nginx mengevaluasi if, kondisi terpenuhi, dan request dimasukkan ke anonymous location. Karena anonymous location ini tidak mewarisi proxy_pass http://backend; dari luar, Nginx bingung bagaimana cara memproses request tersebut. Akibatnya, Nginx mencoba menyajikan file statis dari root folder default, gagal menemukan file, dan mengembalikan error 404 Not Found kepada klien, padahal backend sedang aktif berjalan.

Skenario Penggunaan if yang 100% Aman #

Satu-satunya penggunaan if yang dijamin 100% aman oleh tim inti Nginx adalah jika di dalam blok if tersebut kita hanya menggunakan dua directive berikut:

  1. return ... (Mengembalikan respons instan, seperti return 301 atau return 403).
  2. rewrite ... last; (Menghentikan rewrite phase dan mengulang pencarian location).

Kedua directive ini aman karena mereka langsung memutus alur pemrosesan request dan mengembalikan kendali ke event loop Nginx secara instan, sehingga anonymous location tersembunyi tidak sempat memicu masalah pewarisan.

# PENGGUNAAN AMAN: Hanya berisi return di level server context
server {
    listen 80;
    server_name example.com;

    # Aman karena langsung keluar via return 301
    if ($host = 'www.example.com') {
        return 301 https://example.com$request_uri;
    }
}

Ringkasan #

  • Variabel Nginx dievaluasi secara dinamis (on-the-fly) per request, bukan dialokasikan secara statik pada startup sistem.
  • Selalu gunakan $host alih-alih $http_host saat meneruskan header Host ke upstream backend untuk menghindari serangan Host Header Injection dan membersihkan informasi port non-standar.
  • $request_time mengukur durasi siklus request penuh dari sudut pandang klien (sensitif terhadap bandwidth lambat), sedangkan $upstream_response_time murni mengukur performa komputasi backend kita.
  • Distributed Tracing dapat diaktifkan secara instan menggunakan variabel $request_id sebagai Correlation ID global di antara mikroservis.
  • Modul map memanfaatkan lazy evaluation (hanya memproses jika dipanggil) dan tabel hash cepat di memori, menjadikannya pengganti blok if yang sangat efisien dan aman.
  • Hindari if di dalam location block kecuali jika blok tersebut hanya berisi instruksi return atau rewrite ... last guna menghindari bug fatal akibat pembuatan anonymous location tersembunyi.

← Sebelumnya: Location Block   Berikutnya: Serving Static Files →

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