Konfigurasi Reverse Proxy

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:port adalah bentuk paling sederhana — Caddy menangani connection pooling, forwarding header, dan timeout secara otomatis.
  • Gunakan lb_policy least_conn untuk 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 -1 diperlukan untuk streaming response (SSE, chunked transfer) — tanpanya response akan di-buffer dan tidak real-time.
  • WebSocket proxying bekerja otomatis — Caddy mendeteksi Upgrade: websocket dan 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 →

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