Konfigurasi Reverse Proxy #
Direktif reverse_proxy adalah salah satu yang paling feature-rich di Caddy. Di balik sintaks yang sederhana, terdapat mesin proxy yang sangat capable dengan load balancing, health checking, circuit breaking, retry logic, dan banyak lagi — semua dapat dikonfigurasi via Caddyfile.
Bentuk Paling Sederhana #
example.com {
# Proxy semua request ke backend di port 3000
reverse_proxy localhost:3000
}
Hanya satu baris ini sudah memberikan:
- Proxy ke backend dengan HTTP/1.1 atau HTTP/2
- Connection pooling otomatis
- Header forwarding (Host, X-Forwarded-For, dll.)
- Timeout default yang reasonable
- Error handling dasar
Multiple Upstream — Load Balancing #
example.com {
# Load balancing ke 3 instance (round-robin secara default)
reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000
# Atau dengan konfigurasi lebih detail
reverse_proxy {
to backend-1:3000
to backend-2:3000
to backend-3:3000
lb_policy round_robin # Default
}
}
Load Balancing Policies #
example.com {
reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000 {
# round_robin (default)
# Distribusi bergantian: 1, 2, 3, 1, 2, 3...
lb_policy round_robin
# least_conn
# Kirim ke upstream dengan koneksi aktif paling sedikit
# Lebih baik untuk request dengan durasi bervariasi
lb_policy least_conn
# random
# Pilih upstream secara acak
lb_policy random
# random_choose 2
# Pilih 2 upstream secara acak, lalu kirim ke yang least_conn
# Balance antara random dan least_conn
lb_policy random_choose 2
# ip_hash
# Client dengan IP sama selalu ke upstream yang sama (sticky session)
lb_policy ip_hash
# uri_hash
# Request ke URI yang sama selalu ke upstream yang sama
# Berguna untuk cache consistency
lb_policy uri_hash
# header X-User-ID
# Hash berdasarkan nilai header tertentu
lb_policy header X-User-ID
# cookie session_id
# Sticky session via cookie
lb_policy cookie {
name session_id
secret "change-me-in-production"
}
# first
# Selalu coba upstream pertama, gunakan yang lain hanya jika down
lb_policy first
}
}
Health Checks #
Health check memastikan Caddy hanya mengirim traffic ke backend yang sehat. Ada dua jenis: aktif (Caddy secara proaktif mengecek) dan pasif (Caddy mengamati error dari request nyata).
Health Check Aktif #
example.com {
reverse_proxy backend-1:3000 backend-2:3000 {
# Aktif health check — Caddy kirim request berkala
health_uri /health # Endpoint yang dicek
health_port 3000 # Port untuk health check (opsional)
health_interval 10s # Seberapa sering cek (default: 30s)
health_timeout 5s # Timeout per check (default: 5s)
health_status 200 # Status code yang dianggap sehat
# Cek dengan header kustom
health_headers {
Accept "application/json"
X-Health-Check "caddy"
}
# Cek body response berisi string tertentu
health_body "\"status\":\"ok\""
}
}
Health Check Pasif #
example.com {
reverse_proxy backend-1:3000 backend-2:3000 {
# Pasif health check — observasi dari request nyata
health_fails 3 # Tandai unhealthy setelah 3 kali gagal berturut-turut
health_passes 2 # Pulihkan setelah 2 kali sukses berturut-turut
# Atur max_fails dan fail_duration bersama (lebih sering dipakai)
# Jika 3 kali gagal dalam 30 detik → tandai unhealthy
# max_fails 3
# fail_duration 30s
# Unhealthy threshold — jika lebih dari N% request gagal dalam window waktu
# unhealthy_latency 500ms # Anggap unhealthy jika latensi > 500ms
# unhealthy_status 500 503 # Status code yang dianggap tidak sehat
}
}
Retry dan Circuit Breaker #
example.com {
reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000 {
# Retry ke upstream lain jika yang pertama gagal
lb_try_duration 5s # Coba selama maksimal 5 detik
lb_try_interval 250ms # Jeda antara retry
# Retry hanya untuk status code tertentu
# (default: retry jika koneksi gagal, bukan jika dapat response)
}
}
Timeout Configuration #
example.com {
reverse_proxy backend:3000 {
transport http {
# Timeout saat membuat koneksi ke backend
dial_timeout 5s
# Timeout untuk menerima response header dari backend
response_header_timeout 30s
# Timeout membaca body dari backend
# (default: tidak terbatas — untuk streaming)
# read_buffer_size 4kb
# Timeout total request ke backend (koneksi + response)
# Hati-hati: nilai ini harus cukup untuk request yang paling lama
}
}
}
Flush Interval — Streaming Response #
Untuk aplikasi yang mengirim response secara streaming (Server-Sent Events, chunked transfer), penting mengkonfigurasi flush interval:
app.example.com {
reverse_proxy backend:3000 {
# Flush setiap -1ms berarti flush segera (untuk streaming)
# Default: 100ms
flush_interval -1
}
}
# Contoh: Streaming API / SSE
sse.example.com {
reverse_proxy backend:3000 {
# Untuk Server-Sent Events: flush segera
flush_interval -1
# Header yang diperlukan untuk SSE
header_up Cache-Control "no-cache"
header_up X-Accel-Buffering "no"
}
}
WebSocket Proxying #
Caddy secara otomatis mendeteksi dan menangani WebSocket upgrade — tidak perlu konfigurasi khusus dalam banyak kasus:
app.example.com {
# WebSocket otomatis di-proxy dengan benar
# Caddy mendeteksi Upgrade: websocket header
reverse_proxy backend:3000
}
Untuk kontrol lebih detail atau jika ada masalah:
app.example.com {
# Route WebSocket secara terpisah
@ws {
header Upgrade websocket
}
@http {
not header Upgrade websocket
}
# WebSocket: flush segera, tidak ada timeout body
reverse_proxy @ws backend:3000 {
transport http {
dial_timeout 5s
# Jangan set response_header_timeout untuk WebSocket
}
flush_interval -1
}
# HTTP biasa
reverse_proxy @http backend:3000 {
transport http {
dial_timeout 5s
response_header_timeout 30s
}
}
}
Upstream dengan Path Rewrite #
Sering kali kamu perlu strip prefix dari path sebelum meneruskan ke backend:
example.com {
# Request ke /api/users diteruskan ke backend sebagai /users
handle /api/* {
uri strip_prefix /api
reverse_proxy backend:8080
}
# Request ke /app/* diteruskan ke frontend
handle /app/* {
uri strip_prefix /app
reverse_proxy frontend:3000
}
# Root
handle {
root * /var/www/html
file_server
}
}
Upstream Dinamis via dynamic
#
Untuk kasus khusus di mana daftar upstream perlu di-resolve secara dinamis:
example.com {
reverse_proxy {
dynamic a {
# Resolve via DNS A record
name backend.example.com
port 3000
# Re-resolve setiap 30 detik
refresh 30s
# Resolvers yang digunakan
resolvers 8.8.8.8 1.1.1.1
# Fallback jika DNS resolve gagal
fallback 127.0.0.1:3000
}
}
}
Konfigurasi untuk Upstream HTTPS #
Jika backend berjalan dengan HTTPS (misalnya internal service dengan self-signed cert):
example.com {
reverse_proxy https://internal-service:8443 {
transport http {
tls
# Jika backend pakai self-signed atau private CA
tls_insecure_skip_verify # HANYA untuk testing/development
# Cara yang benar: specify CA cert
tls_trusted_ca_certs /path/to/ca.pem
# Client certificate (mutual TLS)
tls_client_auth /path/to/client.pem /path/to/client-key.pem
# SNI untuk backend
tls_server_name internal-service
}
}
}
Debug dan Monitoring Reverse Proxy #
# Lihat status upstream via Admin API
curl -s http://localhost:2019/reverse_proxy/upstreams/ | jq .
# Output contoh:
# [
# {
# "address": "backend-1:3000",
# "num_requests": 1234,
# "fails": 0
# },
# {
# "address": "backend-2:3000",
# "num_requests": 1198,
# "fails": 2
# }
# ]
# Tandai upstream sebagai unhealthy secara manual (untuk drain/maintenance)
curl -X POST "http://localhost:2019/reverse_proxy/upstreams/" \
-H "Content-Type: application/json" \
-d '{"address":"backend-2:3000","unhealthy":true}'
Pola Production yang Lengkap #
{
email [email protected]
}
app.example.com {
# Encode response
encode gzip zstd
# Log akses
log {
output file /var/log/caddy/app.log
format json
}
# Reverse proxy ke cluster backend
reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000 {
lb_policy least_conn
# Health checks aktif
health_uri /health
health_interval 10s
health_timeout 5s
health_status 200
# Header forwarding
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
# Retry logic
lb_try_duration 5s
lb_try_interval 200ms
# Transport
transport http {
dial_timeout 5s
response_header_timeout 30s
}
}
# Error pages
handle_errors {
@down expression {err.status_code} in [502, 503, 504]
handle @down {
respond "Service sedang tidak tersedia, silakan coba beberapa saat lagi." 503
}
}
}
Ringkasan #
reverse_proxy upstream:portadalah bentuk paling sederhana — Caddy menangani connection pooling, forwarding header, dan timeout secara otomatis.- Gunakan
lb_policy least_connuntuk load balancing yang lebih adil pada request dengan durasi bervariasi — lebih baik dari round_robin default.- Health check aktif (
health_uri /health) adalah best practice production — Caddy tidak akan kirim traffic ke backend yang gagal health check.flush_interval -1diperlukan untuk streaming response (SSE, chunked transfer) — tanpanya response akan di-buffer dan tidak real-time.- WebSocket proxying bekerja otomatis — Caddy mendeteksi
Upgrade: websocketdan menanganinya dengan benar tanpa konfigurasi tambahan.- Monitor status upstream via Admin API (
/reverse_proxy/upstreams/) untuk melihat health, jumlah request, dan error count secara real-time.
← Sebelumnya: Konsep Reverse Proxy Berikutnya: Proxy Headers →