NGINX Config Language From First Principles
Learn NGINX In Action - Part 005
Bahasa konfigurasi NGINX dari first principles: directive, block, context, include, parse-time vs runtime, module ownership, validasi, dan pola konfigurasi production-grade.
Part 005 — Bahasa Konfigurasi NGINX dari First Principles
Banyak engineer bisa menulis konfigurasi NGINX yang "jalan". Sedikit yang bisa menjelaskan kenapa konfigurasi itu jalan, kapan ia dievaluasi, bagian mana yang diwariskan, bagian mana yang mengganti parent, dan bagian mana yang sebenarnya dimiliki oleh modul berbeda.
Itu masalah serius.
NGINX bukan framework yang mengeksekusi file konfigurasi baris demi baris seperti script. NGINX membaca konfigurasi, memvalidasi syntax/context/directive, membangun struktur konfigurasi internal, membuka resource runtime, lalu worker menggunakan hasilnya untuk memproses koneksi.
Part ini membangun mental model bahasa konfigurasi NGINX dari bawah.
Referensi resmi:
https://nginx.org/en/docs/beginners_guide.htmlhttps://docs.nginx.com/nginx/admin-guide/basic-functionality/managing-configuration-files/https://nginx.org/en/docs/ngx_core_module.htmlhttps://nginx.org/en/docs/http/ngx_http_core_module.htmlhttps://nginx.org/en/docs/dirindex.html
1. Mental model utama
File nginx.conf bukan sekadar file teks.
Lebih tepatnya:
nginx.confadalah input untuk compiler konfigurasi NGINX yang membangun runtime traffic engine.
Konsekuensinya:
- directive tidak "dijalankan" seperti statement imperative;
- banyak error muncul saat parse/reload, bukan saat request;
- beberapa nilai diputuskan saat startup/reload;
- beberapa nilai baru dihitung saat request;
- konteks menentukan apakah directive valid;
- module menentukan makna directive;
- reload aman hanya terjadi jika konfigurasi baru lolos validasi dan resource baru bisa dibuka.
Kalau mental model Anda masih "NGINX membaca dari atas ke bawah lalu menjalankan perintah", Anda akan mudah salah saat menghadapi location, rewrite, include, map, proxy_pass, add_header, try_files, dan if.
2. Bentuk paling dasar: directive
Unit terkecil konfigurasi NGINX adalah directive.
Ada dua bentuk besar:
# simple directive
worker_processes auto;
# block directive
http {
server {
listen 80;
}
}
Simple directive:
name argument1 argument2 ...;
Block directive:
name argument1 argument2 ... {
nested_directives;
}
Aturan praktis:
- simple directive berakhir dengan
;; - block directive membuka
{dan menutup}; - directive berada dalam context tertentu;
- directive hanya valid jika module pemilik directive mengizinkannya di context tersebut;
- komentar dimulai dengan
#sampai akhir baris; - whitespace hanya pemisah token, bukan struktur seperti YAML.
Contoh minimal:
worker_processes auto;
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name example.com;
location / {
return 200 "hello\n";
}
}
}
Ini bukan "program". Ini deklarasi bentuk runtime.
3. Context: ruang tempat directive boleh hidup
NGINX menggunakan context untuk mengatur scope.
Context umum:
Struktur HTTP paling umum:
# main context
worker_processes auto;
events {
# events context
worker_connections 4096;
}
http {
# http context
include mime.types;
upstream app_backend {
# upstream context
server 10.0.1.10:8080;
server 10.0.1.11:8080;
}
server {
# server context
listen 443 ssl http2;
server_name api.example.com;
location /api/ {
# location context
proxy_pass http://app_backend;
}
}
}
Context bukan dekorasi. Context adalah bagian dari grammar.
Contoh salah:
worker_connections 4096;
worker_connections bukan directive main context. Ia milik events context.
Contoh salah lain:
http {
location / {
return 200;
}
}
location tidak langsung valid di http; ia valid di dalam server.
4. Directive dimiliki module
Setiap directive datang dari module tertentu.
| Directive | Module/konteks konseptual | Fungsi |
|---|---|---|
worker_processes | core | jumlah worker process |
worker_connections | event core | batas koneksi per worker |
server | HTTP core / stream core / mail core | mendefinisikan virtual server |
location | HTTP core | memilih konfigurasi berdasarkan URI |
proxy_pass | HTTP proxy module | meneruskan request ke upstream HTTP |
grpc_pass | HTTP gRPC module | meneruskan request gRPC |
fastcgi_pass | FastCGI module | meneruskan request FastCGI |
ssl_certificate | SSL module | certificate TLS |
limit_req | limit request module | rate limiting |
map | map module | membuat variable hasil mapping |
Konsekuensi production:
- unknown directive sering berarti module tidak tersedia, bukan sekadar typo.
- Config yang valid di satu build NGINX bisa gagal di build lain.
- Container image berbeda bisa membawa compile options berbeda.
- Dynamic module perlu dimuat sebelum directive-nya dipakai.
- Dokumentasi directive harus selalu dibaca bersama context dan module pemiliknya.
Cek build options:
nginx -V
Output nginx -V biasanya menunjukkan versi, compiler, configure arguments, module, path default, dan OpenSSL build/runtime info.
Untuk production, simpan output nginx -V sebagai artifact debugging. Ketika ada incident, pertanyaan "kenapa directive ini tidak ada?" sering dijawab oleh nginx -V, bukan oleh isi nginx.conf.
5. Parse-time vs runtime
Tidak semua hal dievaluasi pada waktu yang sama.
Ada yang diputuskan saat parse/startup/reload:
- syntax directive;
- apakah directive valid di context tersebut;
- apakah file include ditemukan;
- apakah listen socket bisa dibuka;
- apakah certificate file bisa dibaca;
- apakah shared memory zone bisa dibuat;
- apakah upstream block syntactically valid;
- apakah module directive tersedia.
Ada yang diputuskan saat request:
- nilai variable seperti
$host,$uri,$remote_addr; - hasil
mapberdasarkan variable input; - location final setelah URI normalization/internal redirect;
- upstream response status;
- cache HIT/MISS/BYPASS;
- header yang dikirim upstream;
- request body size aktual;
- rate limit key aktual.
Kesalahan umum:
set $backend "http://app_backend";
proxy_pass $backend;
Ini terlihat sederhana, tetapi penggunaan variable di proxy_pass mengubah beberapa semantics penting, termasuk bagaimana URI diteruskan dan bagaimana DNS/upstream resolution bekerja. Ini akan dibahas detail di part reverse proxy. Yang penting di sini: variable membuat sebagian keputusan berpindah dari parse-time ke request-time.
6. File utama dan include
NGINX biasanya mulai dari satu file utama:
/etc/nginx/nginx.conf
Tetapi lokasi aktual bergantung pada distribusi, package, compile options, dan container image.
Cek dengan:
nginx -V 2>&1 | tr ' ' '\n' | grep conf-path
Atau dump seluruh konfigurasi efektif:
nginx -T
include memasukkan file lain ke posisi tempat directive itu ditulis.
http {
include /etc/nginx/mime.types;
include /etc/nginx/conf.d/*.conf;
}
Prinsip production:
- gunakan prefix angka jika urutan penting;
- jangan sembunyikan dependency antar file;
- jangan mencampur global defaults, upstreams, dan virtual servers tanpa struktur;
- jangan membuat include recursive atau terlalu dalam;
- gunakan
nginx -Tdi CI agar konfigurasi efektif terlihat utuh; - jangan bergantung pada asumsi packaging tanpa memverifikasi path.
Layout yang lebih aman:
/etc/nginx/
├── nginx.conf
├── mime.types
├── modules-enabled/
├── conf.d/
│ ├── 00-global-log-format.conf
│ ├── 05-global-maps.conf
│ ├── 10-upstreams.conf
│ ├── 20-security-defaults.conf
│ └── 90-servers.conf
├── snippets/
│ ├── proxy-common.conf
│ ├── security-headers.conf
│ └── tls-modern.conf
└── sites-enabled/
├── api.example.com.conf
└── static.example.com.conf
Tetapi layout bukan agama. Yang penting adalah invariant:
Orang yang membaca config harus bisa tahu directive mana global, mana default, mana per-host, mana per-location, dan mana snippet reusable.
7. nginx -t dan nginx -T
Dua command ini wajib masuk muscle memory.
Validasi syntax dan resource dasar:
nginx -t
Dump konfigurasi efektif:
nginx -T
Contoh pipeline deployment minimal:
set -euo pipefail
nginx -t
nginx -T > /tmp/nginx.effective.conf
systemctl reload nginx
Dalam CI container:
docker run --rm \
-v "$PWD/nginx:/etc/nginx:ro" \
nginx:stable \
nginx -t
Yang dicek nginx -t:
- syntax;
- directive dikenal atau tidak;
- directive valid pada context;
- jumlah argument valid;
- sebagian resource path bisa dibuka;
- certificate/key dapat dibaca;
- sebagian conflict listen/server dapat terdeteksi.
Yang tidak bisa dijamin nginx -t:
- upstream benar-benar sehat;
- DNS runtime selalu resolve;
- certificate belum kedaluwarsa secara policy bisnis;
- routing tidak salah secara domain logic;
- cache key tidak bocor antar user;
- rate limit tidak terlalu agresif;
- header security sudah sesuai semua response path;
- load balancing sesuai kapasitas nyata.
nginx -t adalah pagar syntax, bukan bukti arsitektur benar.
8. Directive arguments: string, size, time, flag, enum
Directive NGINX menerima argument dalam bentuk token.
autoindex off;
client_max_body_size 20m;
proxy_buffer_size 16k;
proxy_connect_timeout 3s;
proxy_read_timeout 60s;
keepalive_timeout 15s;
error_log /var/log/nginx/error.log warn;
listen 443 ssl http2;
root /srv/www/app/current/public;
Aturan praktis:
- jangan menganggap semua angka satuannya sama;
- baca dokumentasi directive untuk default unit;
- gunakan unit eksplisit (
s,m,k) untuk mengurangi ambiguitas; - quote string yang mengandung spasi atau karakter rawan;
- hindari variable di directive yang sebenarnya seharusnya static kecuali semantics-nya dipahami.
Contoh aman:
add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'none'" always;
Tanpa quote, token akan pecah dan directive menjadi invalid atau bermakna lain.
9. Variable bukan variable seperti bahasa pemrograman umum
NGINX memiliki variable seperti:
$host
$uri
$request_uri
$remote_addr
$scheme
$server_name
$upstream_status
$upstream_response_time
Tetapi variable NGINX bukan variable mutable biasa. Banyak variable adalah nilai request/runtime yang disediakan module.
Contoh:
log_format main '$remote_addr $host "$request" '
'$status $body_bytes_sent '
'upstream=$upstream_addr upstream_status=$upstream_status';
Variable bisa dipakai untuk logging, routing, cache key, rate limit key, header forwarding, conditional response, dynamic upstream selection, dan rewrite flow.
Tetapi variable membawa risiko:
- evaluation timing berubah;
- caching key bisa membesar atau bocor;
- upstream selection bisa menjadi request-time;
- DNS behavior bisa berubah;
- config lebih sulit diverifikasi statically;
- log terlihat benar tetapi routing salah.
Prinsip:
Gunakan variable untuk data request. Jangan gunakan variable untuk menyembunyikan desain routing yang seharusnya eksplisit.
Contoh baik:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
Contoh rawan:
set $target http://$http_x_target;
proxy_pass $target;
Ini membuka permukaan SSRF/proxy abuse jika header tidak dikontrol ketat.
10. map sebagai tabel keputusan, bukan if-else acak
map biasanya berada di context http. Ia membuat variable baru berdasarkan variable input.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
map $host $tenant_name {
default "unknown";
api.example.com "api";
admin.example.com "admin";
}
Kemudian digunakan:
proxy_set_header Connection $connection_upgrade;
add_header X-Tenant $tenant_name always;
Kenapa map sering lebih baik daripada if?
- decision table terlihat eksplisit;
- bisa ditempatkan di
httpsebagai global policy; - mudah dites dengan request matrix;
- mengurangi nested imperative flow;
- memisahkan policy derivation dari action.
Tetapi map tetap harus diperlakukan sebagai policy table. Jangan biarkan ia menjadi dumping ground semua keputusan tanpa naming yang jelas.
11. if di NGINX: bukan if biasa
if di NGINX adalah directive dari rewrite module dan semantics-nya historis. Ia sering disalahgunakan seolah-olah seperti if di Java/Go/JavaScript.
Contoh yang relatif aman:
if ($request_method = POST) {
return 405;
}
Contoh yang rawan:
location / {
if ($http_x_debug = "1") {
proxy_pass http://debug_backend;
}
proxy_pass http://normal_backend;
}
Jangan menjadikan if sebagai router umum. Gunakan server_name untuk host routing, location untuk path routing, map untuk derived policy, return untuk redirect/deny sederhana, upstream block untuk backend set, atau dedicated gateway/app logic jika keputusan sudah terlalu domain-specific.
Prinsip:
Di NGINX, routing yang baik biasanya berbentuk declarative selection, bukan nested imperative branch.
Part rewrite dan routing akan membahas ini lebih detail.
12. Config bukan tempat semua business logic
NGINX powerful, tetapi bukan application framework.
Ia cocok untuk host/path routing, TLS termination, request/response header normalization, buffering, compression, cache, rate limiting, IP allow/deny, basic auth/mTLS, reverse proxy, load balancing, static asset serving, simple redirects, dan edge-level policy.
Ia buruk untuk workflow bisnis kompleks, authorization fine-grained berbasis domain object, conditional logic panjang, user-specific dynamic decision tanpa backend authority, transformation body kompleks, stateful orchestration, audit domain logic, dan compensation logic.
NGINX harus menjadi traffic policy layer, bukan tempat menyembunyikan domain model.
13. Production configuration skeleton
Skeleton awal:
user nginx nginx;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log warn;
events {
worker_connections 4096;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main escape=json
'{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"host":"$host",'
'"request":"$request",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_addr":"$upstream_addr",'
'"upstream_status":"$upstream_status",'
'"upstream_response_time":"$upstream_response_time"'
'}';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 15s;
client_max_body_size 10m;
include /etc/nginx/conf.d/*.conf;
}
Contoh upstream + server:
upstream app_backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name api.local.test;
location /healthz {
access_log off;
return 200 "ok\n";
}
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://app_backend;
}
}
Belum sempurna. Belum hardened. Belum TLS. Belum cache. Tetapi sudah memperlihatkan struktur inti: core runtime di main/events, HTTP defaults di http, backend pool di upstream, virtual host di server, dan route behavior di location.
14. Config smell yang harus langsung dicurigai
14.1 Semua config ditaruh dalam satu file panjang
Bukan otomatis salah, tetapi sulit di-review.
nginx.conf 2500 lines
Risiko: policy bercampur dengan host config, upstream sulit dilacak, duplicate directive tidak terlihat, rollback partial sulit, dan review PR noise tinggi.
14.2 Snippet dipakai tanpa tahu context
include snippets/proxy-common.conf;
Jika snippet berisi directive proxy_set_header, ia valid di http, server, atau location tergantung directive. Tetapi efek inheritance-nya berbeda.
Nama snippet harus menjelaskan target context:
snippets/http-log-format.conf
snippets/server-security-headers.conf
snippets/location-proxy-common.conf
14.3 Variable dipakai untuk semua hal
set $service "app";
set $backend "http://$service.internal";
proxy_pass $backend;
Risiko: DNS/request-time behavior berubah, upstream keepalive behavior bisa tidak sesuai ekspektasi, SSRF jika input datang dari client, dan static validation makin lemah.
14.4 Duplicate defaults tersebar
proxy_read_timeout 60s;
# ... 400 lines later
proxy_read_timeout 300s;
Tidak semua duplicate salah. Tetapi duplicate tanpa alasan adalah tanda tidak ada ownership.
14.5 Menggunakan if untuk routing utama
if ($host = api.example.com) {
proxy_pass http://api;
}
Host routing seharusnya server_name, bukan if.
15. Cara membaca konfigurasi NGINX orang lain
Gunakan urutan ini:
- Cari
nginx -Tjika ada, bukan hanya file partial. - Identifikasi main context: user, worker, logs, pid, modules.
- Identifikasi
events: worker connections dan event method jika eksplisit. - Identifikasi
http: defaults, logs, maps, MIME, gzip, limits, cache zones. - Identifikasi
upstream: backend pools, keepalive, weights, fail parameters. - Identifikasi
server: listen, TLS, server_name, default_server. - Identifikasi
location: routing, static/proxy/cache/rate-limit. - Cari
include: pahami posisi include, bukan hanya isi file. - Cari
if,rewrite,return: pahami control flow URI. - Cari variable di
proxy_pass,root,alias,add_header,proxy_set_header. - Cari directive list yang inheritance-nya sering mengejutkan:
add_header,proxy_set_header,access_log,error_page. - Cari hardcoded domain/IP/secret/path.
- Cari perbedaan antara expected architecture dan actual config.
Checklist cepat:
nginx -T | grep -nE 'listen|server_name|location|proxy_pass|root|alias|rewrite|return|if|add_header|proxy_set_header|limit_req|proxy_cache'
Ini bukan pengganti review, tetapi mempercepat orientasi.
16. CI validation untuk konfigurasi
Minimum CI:
#!/usr/bin/env bash
set -euo pipefail
IMAGE="nginx:stable"
CONFIG_DIR="$PWD/nginx"
docker run --rm \
-v "$CONFIG_DIR:/etc/nginx:ro" \
"$IMAGE" \
nginx -t
Lebih baik:
#!/usr/bin/env bash
set -euo pipefail
IMAGE="registry.example.com/platform/nginx-runtime:1.30"
CONFIG_DIR="$PWD/nginx"
OUT_DIR="$PWD/build/nginx"
mkdir -p "$OUT_DIR"
docker run --rm \
-v "$CONFIG_DIR:/etc/nginx:ro" \
"$IMAGE" \
nginx -T > "$OUT_DIR/effective-nginx.conf"
docker run --rm \
-v "$CONFIG_DIR:/etc/nginx:ro" \
"$IMAGE" \
nginx -t
Tambahkan policy checks:
# no accidental default_server without explicit marker
grep -R "default_server" nginx/ | grep -v "INTENTIONAL_DEFAULT_SERVER" && exit 1
# no direct proxy_pass from client-controlled header
grep -R "proxy_pass.*\$http_" nginx/ && exit 1
# no old TLS versions
grep -R "ssl_protocols.*TLSv1" nginx/ && exit 1
Policy grep bukan verifikasi sempurna. Tetapi cukup untuk menangkap kesalahan kelas tertentu sebelum production.
17. Lab: membangun config kecil yang bisa dites
Struktur:
lab-nginx-config/
├── nginx.conf
└── conf.d/
├── 10-upstream.conf
└── 20-server.conf
nginx.conf:
worker_processes auto;
error_log /var/log/nginx/error.log notice;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr $host "$request" '
'$status request_time=$request_time '
'upstream=$upstream_addr upstream_status=$upstream_status';
access_log /var/log/nginx/access.log main;
include /etc/nginx/conf.d/*.conf;
}
conf.d/10-upstream.conf:
upstream demo_backend {
server host.docker.internal:8080;
keepalive 16;
}
conf.d/20-server.conf:
server {
listen 8081;
server_name _;
location = /healthz {
access_log off;
return 200 "ok\n";
}
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://demo_backend;
}
}
Test config:
docker run --rm \
-v "$PWD/lab-nginx-config:/etc/nginx:ro" \
nginx:stable \
nginx -t
Dump effective config:
docker run --rm \
-v "$PWD/lab-nginx-config:/etc/nginx:ro" \
nginx:stable \
nginx -T
Eksperimen:
- Pindahkan
worker_connectionske main context. Lihat error. - Hapus
;setelahworker_processes auto. Lihat error. - Letakkan
location /langsung dihttp. Lihat error. - Pakai directive
grpc_passpada image yang mungkin tidak menyertakan module. Cek error. - Tambahkan include file yang tidak ada. Lihat error.
- Tambahkan variable di
proxy_pass. Amati perbedaan behavior pada part reverse proxy nanti.
Tujuannya bukan menghafal error message. Tujuannya membangun intuisi bahwa NGINX memiliki grammar, context, module ownership, dan timing.
18. Production invariants
Sebelum konfigurasi NGINX dianggap layak production, minimal invariant ini harus benar:
- Semua config bisa divalidasi dengan runtime image yang sama dengan production.
nginx -Tdisimpan sebagai artifact build/deploy.- File include punya ownership dan urutan jelas.
- Global default tidak disembunyikan di snippet location.
- Snippet diberi nama sesuai target context.
- Tidak ada
proxy_passberbasis input client tanpa allowlist ketat. iftidak dipakai sebagai routing engine utama.- Semua
serverpunyaserver_nameeksplisit atau sengaja menjadi default. - Semua
listenpenting jelas port/protocol/default-nya. - Timeout, body size, buffering, header, dan log format punya default sadar.
- Semua directive yang bergantung module diverifikasi oleh
nginx -Vatau image contract. - Config reload punya rollback path.
- Config review memeriksa semantics, bukan hanya syntax.
19. Ringkasan
Bahasa konfigurasi NGINX terlihat kecil, tetapi semantics-nya dalam.
Mental model yang harus dibawa:
- directive adalah unit konfigurasi;
- block directive membuat context;
- context menentukan validitas;
- module menentukan arti directive;
- include adalah ekspansi konfigurasi, bukan namespace;
- parse-time berbeda dari request-time;
- variable menggeser sebagian keputusan ke runtime;
nginx -tmenjaga syntax/resource dasar, bukan membuktikan desain benar;- config production harus bisa dibaca sebagai architecture artifact.
Kalimat kunci:
NGINX config yang baik bukan yang paling pendek. NGINX config yang baik adalah yang semantics-nya eksplisit, scope-nya jelas, dan failure mode-nya bisa diprediksi.
Part berikutnya membahas context dan inheritance lebih dalam. Ini penting karena directive yang sama bisa terlihat "global", tetapi efek aktualnya bergantung pada aturan merge module-specific.
You just completed lesson 05 in start here. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.
Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.