Proxy Cache #
Nginx bisa menyimpan response dari backend ke disk dan melayaninya langsung tanpa menghubungi backend lagi untuk request berikutnya. Ketika dikonfigurasi dengan benar, ini bisa mengurangi beban backend secara dramatis — request yang seharusnya menekan database atau menjalankan kalkulasi berat cukup diproses sekali, hasilnya disimpan, dan jutaan request berikutnya dilayani langsung dari cache.
Tapi caching yang salah dikonfigurasi bisa menyebabkan bug yang sangat berbahaya: pengguna A mendapat data pengguna B, atau perubahan yang seharusnya terlihat segera tetap menampilkan data lama. Artikel ini membahas cara kerjanya secara mendalam beserta strategi aman.
Cara Kerja Proxy Cache #
flowchart TD
REQ["Request masuk\nGET /api/products"] --> CHK{"Ada di cache\n& belum expired?"}
CHK -- "HIT" --> SERVE["Nginx kirim response\nlangsung dari cache\n(backend tidak dihubungi)"]
CHK -- "MISS" --> LOCK{"proxy_cache_lock:\nAda request lain\nyang sudah ke backend\nuntuk key ini?"}
LOCK -- "Ya" --> WAIT["Tunggu di queue\n(proxy_cache_lock_timeout)"]
WAIT --> CHK2{"Sekarang ada\ndi cache?"}
CHK2 -- "Ya" --> SERVE
CHK2 -- "Tidak" --> BACKEND
LOCK -- "Tidak" --> BACKEND["Nginx request ke backend\nBackend proses & kirim response"]
BACKEND --> STORE{"Simpan ke cache?\nCek bypass & no_cache conditions"}
STORE -- "Ya" --> CACHE["Simpan ke disk cache\n/var/cache/nginx/..."]
STORE -- "Tidak (bypass)" --> NOCACHE["Langsung kirim\nke klien, tidak disimpan"]
CACHE --> SERVE2["Kirim response ke klien"]
style SERVE fill:none,stroke-width:2px
style SERVE2 fill:none,stroke-width:2px
style NOCACHE fill:none,stroke-width:2pxLangkah 1: Mendefinisikan Cache Zone #
Sebelum bisa menggunakan cache di location, kita perlu mendefinisikan zone cache di level http. Zone ini mendefinisikan di mana cache disimpan dan berapa besar.
http {
# Satu zone untuk konten publik (API, halaman, dsb)
proxy_cache_path /var/cache/nginx/public
levels=1:2
keys_zone=public_cache:10m
max_size=2g
inactive=1h
use_temp_path=off;
# Zone terpisah untuk API response
proxy_cache_path /var/cache/nginx/api
levels=1:2
keys_zone=api_cache:5m
max_size=500m
inactive=10m
use_temp_path=off;
}
Penjelasan parameter:
| Parameter | Contoh | Artinya |
|---|---|---|
| Path | /var/cache/nginx/public | Direktori tempat menyimpan file cache |
levels=1:2 | levels=1:2 | Struktur subdirektori 2 level (mencegah terlalu banyak file dalam satu direktori) |
keys_zone | public_cache:10m | Nama zone dan ukuran shared memory untuk index. 10MB ≈ 80.000 entry |
max_size | 2g | Batas total ukuran cache di disk. Nginx menghapus entry lama (LRU) jika penuh |
inactive | 1h | Hapus entry yang tidak diakses selama 1 jam, meski belum expired |
use_temp_path=off | - | Tulis langsung ke direktori cache, bukan via temp — lebih efisien |
# Buat direktori cache dengan owner yang benar
sudo mkdir -p /var/cache/nginx/{public,api}
sudo chown nginx:nginx /var/cache/nginx/
sudo chown nginx:nginx /var/cache/nginx/public /var/cache/nginx/api
Langkah 2: Mengaktifkan Cache di Location #
server {
location /api/products/ {
proxy_pass http://backend;
# Gunakan zone yang sudah didefinisikan
proxy_cache api_cache;
# Berapa lama cache berlaku untuk berbagai kode status
proxy_cache_valid 200 30m; # Response sukses: 30 menit
proxy_cache_valid 301 302 5m; # Redirect: 5 menit
proxy_cache_valid 404 1m; # Not found: 1 menit
proxy_cache_valid any 0; # Response lain: tidak di-cache
# Header debug — tampilkan status cache ke klien
# HIT = dari cache, MISS = dari backend, BYPASS = di-skip
add_header X-Cache-Status $upstream_cache_status always;
}
}
Nilai $upstream_cache_status
#
| Nilai | Artinya |
|---|---|
HIT | Response dilayani dari cache |
MISS | Tidak ada di cache, request ke backend |
BYPASS | Cache di-bypass (sesuai kondisi proxy_cache_bypass) |
EXPIRED | Ada di cache tapi sudah expired, request ke backend untuk refresh |
STALE | Melayani cache lama karena backend error (stale response) |
UPDATING | Entry sedang di-refresh oleh request lain (cache lock) |
REVALIDATED | Cache masih valid berdasarkan conditional request |
Cache Key: Apa yang Membedakan Entry Cache #
Nginx menentukan apakah dua request adalah “request yang sama” berdasarkan cache key. Default cache key adalah $scheme$proxy_host$request_uri.
Kita bisa mengkustomisasi key ini untuk menyertakan dimensi lain:
location /api/ {
proxy_pass http://backend;
proxy_cache api_cache;
# ─── Default key (biasanya cukup untuk API publik) ─────────────────────
proxy_cache_key "$scheme$request_method$host$request_uri";
# ─── Key dengan bahasa: konten berbeda berdasarkan Accept-Language ──────
proxy_cache_key "$scheme$host$request_uri$http_accept_language";
# ─── Key dengan versi: untuk A/B testing berdasarkan custom header ──────
proxy_cache_key "$scheme$host$request_uri$http_x_app_version";
# ─── Key berdasarkan cookies tertentu: konten berbeda per user-group ───
# Hati-hati! Ini bisa membuat cache sangat besar jika cookie banyak variasinya
proxy_cache_key "$scheme$host$request_uri$cookie_user_segment";
}
Peringatan: Cache Key yang Buruk #
# BERBAHAYA: key yang menyertakan cookie session
proxy_cache_key "$scheme$host$request_uri$http_cookie";
# → Setiap user dengan session berbeda = entry cache yang berbeda
# → Cache tidak berguna (lebih banyak MISS daripada HIT)
# → Cache bisa bocor data antar user jika cookie tidak di-hash dengan benar
# BENAR: bypass cache untuk user yang terautentikasi (lihat seksi berikutnya)
Strategi Bypass Cache: Kapan Tidak Boleh Cache #
Tidak semua request boleh di-cache. Ini adalah bagian paling penting — cache yang salah strategi bisa menyebabkan data bocor antar pengguna.
Prinsip Dasar #
Cache → OK untuk:
✓ Konten publik yang sama untuk semua pengguna
✓ GET request
✓ Response yang tidak berubah sering
✓ Response dari endpoint yang tidak bergantung pada identity pengguna
Cache → JANGAN untuk:
✗ Response yang personalized (profil user, keranjang belanja)
✗ POST, PUT, DELETE, PATCH (mutating requests)
✗ Request dengan Authorization header atau session cookie
✗ Response yang mengandung data sensitif
✗ Endpoint yang mengembalikan data real-time
Implementasi Bypass Cache #
http {
# ─── Deteksi user terautentikasi berdasarkan cookie ─────────────────────
map $http_cookie $no_cache_cookie {
default 0;
~*session_id= 1; # Ada cookie session
~*auth_token= 1; # Ada cookie auth
~*jwt= 1; # Ada JWT cookie
}
# ─── Deteksi HTTP method yang tidak boleh di-cache ───────────────────────
map $request_method $no_cache_method {
default 0;
POST 1;
PUT 1;
PATCH 1;
DELETE 1;
}
server {
location /api/ {
proxy_pass http://backend;
proxy_cache api_cache;
proxy_cache_valid 200 10m;
# proxy_cache_bypass: jika nilainya non-empty & non-zero → bypass
# (request tetap ke backend, tapi response TIDAK disimpan ke cache)
proxy_cache_bypass $no_cache_cookie $no_cache_method;
# proxy_no_cache: jika nilainya non-empty & non-zero
# → response tidak disimpan ke cache
proxy_no_cache $no_cache_cookie $no_cache_method;
}
# ─── Endpoint publik — selalu bisa di-cache ─────────────────────────
location /api/public/ {
proxy_pass http://backend;
proxy_cache public_cache;
proxy_cache_valid 200 1h;
# Tidak ada bypass — semua user dapat response yang sama
}
# ─── Endpoint private — tidak pernah di-cache ───────────────────────
location /api/user/ {
proxy_pass http://backend;
# Tidak pakai proxy_cache sama sekali untuk endpoint private
}
}
}
Bypass Berdasarkan Header Cache-Control dari Klien #
Browser bisa meminta fresh copy dengan mengirim Cache-Control: no-cache. Kita bisa menghormati request ini:
location /api/ {
proxy_pass http://backend;
proxy_cache api_cache;
# Bypass jika klien meminta fresh copy
# (Ctrl+Shift+R di browser mengirim Cache-Control: no-cache)
proxy_cache_bypass $http_pragma $http_authorization $http_cache_control;
}
Stale Cache: Melayani Cache Lama saat Backend Error #
Salah satu fitur paling berguna — Nginx bisa melayani cache yang sudah expired ketika backend tidak bisa dihubungi. Lebih baik menampilkan data sedikit lama daripada error 502.
location /api/ {
proxy_pass http://backend;
proxy_cache api_cache;
proxy_cache_valid 200 10m;
# Layani cache lama (expired) jika backend:
# - Error (502, 503, 500, 504)
# - Timeout saat konek
# - Sedang di-update oleh request lain
proxy_cache_use_stale error timeout updating
http_500 http_502 http_503 http_504;
# Berapa lama boleh melayani stale cache
# (di luar waktu cache_valid normal)
proxy_cache_revalidate on;
# Thundering herd protection:
# Hanya satu request yang dikirim ke backend untuk refresh cache yang expired
# Request lain menunggu hasil dari satu request itu
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
proxy_cache_lock_age 10s;
}
Background Update: Refresh Cache Secara Diam-Diam #
Untuk konten yang berubah secara periodik, kita bisa memberi tahu Nginx untuk me-refresh cache di background saat konten hampir expired:
location /api/products/ {
proxy_pass http://backend;
proxy_cache api_cache;
proxy_cache_valid 200 5m;
# Layani dari cache SAMBIL refresh di background
# Klien tidak menunggu refresh — mereka langsung dapat response
proxy_cache_background_update on;
# Jangan hapus cache yang expired sebelum ada versi baru
proxy_cache_use_stale updating;
}
Menghormati Cache Headers dari Backend #
Nginx juga bisa menggunakan header HTTP standar dari backend untuk menentukan berapa lama cache berlaku:
# Di backend Node.js:
# res.set('Cache-Control', 'public, max-age=3600, s-maxage=7200');
# s-maxage: khusus untuk shared cache (termasuk Nginx proxy cache)
location /api/ {
proxy_pass http://backend;
proxy_cache api_cache;
# Gunakan nilai Cache-Control dari backend (default)
# Jika backend tidak set Cache-Control, gunakan proxy_cache_valid
# Atau: abaikan Cache-Control dari backend, pakai konfigurasi Nginx
proxy_ignore_headers Cache-Control Expires Set-Cookie;
proxy_cache_valid 200 30m; # Nginx yang kontrol, backend diabaikan
}
Perhatian: jika backend mengirim Set-Cookie, Nginx secara default tidak menyimpan response ke cache (karena cookie biasanya berarti response personal). Untuk mengabaikan ini:
location /api/public/ {
proxy_pass http://backend;
proxy_cache api_cache;
# Abaikan Set-Cookie dari backend (jika yakin endpoint ini tidak personal)
proxy_ignore_headers Set-Cookie;
proxy_cache_valid 200 1h;
# Jangan teruskan Set-Cookie ke klien (karena kita sudah ignore)
proxy_hide_header Set-Cookie;
}
Purging Cache #
Manual via Shell #
# Lihat isi cache
ls -la /var/cache/nginx/api/
# Hapus semua cache (hati-hati — semua request berikutnya akan MISS)
sudo rm -rf /var/cache/nginx/api/*
# Setelah hapus, tidak perlu reload Nginx — ia akan membuat ulang cache secara otomatis
Purge via API (Menggunakan ngx_cache_purge Module) #
# Install modul (Ubuntu/Debian)
sudo apt install libnginx-mod-http-cache-purge
location ~ /purge(/.*) {
# Batasi akses purge hanya dari server internal
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
# Purge cache dengan key pattern yang cocok
proxy_cache_purge public_cache "$scheme$host$1";
}
# Purge endpoint tertentu
curl -X PURGE http://example.com/purge/api/products
# Purge akan menghapus cache entry untuk /api/products
# Request berikutnya ke /api/products akan MISS (fresh dari backend)
Strategi Invalidasi via Backend #
Alternatif yang lebih aman: backend melakukan purge via HTTP request ke Nginx setelah ada update data:
# Python: setelah update produk di database
import requests
def invalidate_product_cache(product_id):
try:
requests.request(
"PURGE",
f"http://nginx-internal/purge/api/products/{product_id}",
timeout=5
)
except Exception as e:
logger.warning(f"Cache purge failed: {e}")
# Tidak kritikal — cache akan expired dengan sendirinya
Monitoring Cache Performance #
# Tambahkan log format yang mencakup cache status
log_format cache_log '$remote_addr [$time_local] "$request" '
'$status $upstream_cache_status '
'$upstream_response_time $request_time';
# Hitung hit rate dari log
# (jalankan setelah beberapa jam traffic)
awk '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# Output contoh:
# 8543 HIT ← 85% hit rate (bagus!)
# 1203 MISS
# 254 BYPASS
# Cache hit rate = HIT / (HIT + MISS) × 100%
# Target: > 80% untuk endpoint publik yang sering diakses
# Cek ukuran cache yang digunakan
du -sh /var/cache/nginx/
# Detail per zone (jika ada log khusus):
nginx -T | grep proxy_cache_path
Ringkasan #
- Definisikan
proxy_cache_pathdi levelhttpsebelum bisa menggunakanproxy_cachedilocation. Gunakan zone terpisah untuk jenis konten yang berbeda.$upstream_cache_statussebagai header debug untuk melihat HIT/MISS/BYPASS — wajib selama fase konfigurasi.- Strategi bypass aman: gunakan
mapdirective untuk mendeteksi user terautentikasi (via cookie/header), lalu setproxy_cache_bypassdanproxy_no_cachebersamaan.proxy_cache_use_stale error timeoutmelayani cache lama saat backend error — membuat aplikasi lebih resilient.proxy_cache_lock onmencegah thundering herd — hanya satu request ke backend saat banyak klien meminta entry cache yang expired bersamaan.- Jangan cache POST/PUT/DELETE, response dengan cookie personal, atau endpoint yang mengandung data sensitif.
- Untuk purge, gunakan modul
ngx_cache_purgeataurm -rf+ biarkan Nginx rebuild cache secara otomatis.