Konfigurasi Reverse Proxy

Konfigurasi Reverse Proxy #

Menyediakan jembatan penghubung yang kokoh antara lalu lintas luar dan aplikasi backend adalah inti dari pengelolaan web server profesional. Direktif reverse_proxy Caddy dirancang untuk menyederhanakan konfigurasi arsitektur ini dengan memadukan kesederhanaan sintaksis dengan fitur tingkat lanjut berskala enterprise. Kita akan mempelajari bagaimana cara mengonfigurasi Caddy untuk mendistribusikan lalu lintas secara cerdas, memantau kesehatan server secara otomatis, dan menangani pemulihan bencana (failover) tanpa mengorbankan stabilitas performa.


Konfigurasi Upstream Tunggal #

Pola paling mendasar dalam penggunaan reverse proxy adalah mengarahkan seluruh lalu lintas dari satu nama domain publik ke satu aplikasi backend tunggal yang berjalan di port lokal.

# Contoh konfigurasi dasar
example.com {
    reverse_proxy localhost:3000
}

Hanya dengan konfigurasi satu baris di atas, Caddy sudah mengaktifkan mekanisme berikut di belakang layar secara otomatis:

  • Connection Pooling: Memelihara koneksi TCP tetap terbuka ke backend :3000 untuk meminimalkan latensi handshake pada request berikutnya.
  • Header Forwarding: Menyisipkan header X-Forwarded-For, X-Forwarded-Proto, dan X-Forwarded-Host secara otomatis agar server backend mengetahui identitas jaringan klien asli.
  • Timeout Default: Menerapkan batas waktu tunggu (timeout) yang aman agar server tidak menggantung saat backend tidak responsif.

Jika server aplikasi kita berjalan di server fisik terpisah di dalam jaringan lokal, kita hanya perlu mengganti localhost dengan alamat IP privat server tersebut:

# Contoh proxy ke IP privat
api.example.com {
    reverse_proxy 10.0.0.15:8080
}

Konfigurasi Multiple Upstream (Load Balancing) #

Ketika aplikasi kita bertumbuh, mengandalkan satu server backend tunggal merupakan titik kegagalan tunggal (Single Point of Failure). Kita dapat mendaftarkan beberapa server backend sekaligus (multiple upstreams) agar Caddy dapat bertindak sebagai penyeimbang beban (load balancer).

# Load balancing ke 3 instance backend
app.example.com {
    reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000
}

Atau kita juga bisa menuliskan daftar backend tersebut menggunakan blok konfigurasi terstruktur dengan direktif to agar Caddyfile kita terlihat lebih rapi:

# Format penulisan terstruktur
app.example.com {
    reverse_proxy {
        to backend-1:3000
        to backend-2:3000
        to backend-3:3000
        
        lb_policy round_robin
    }
}

Kebijakan Load Balancing (Load Balancing Policies) #

Caddy menyediakan berbagai kebijakan algoritma untuk menentukan bagaimana permintaan masuk didistribusikan ke server-server backend:

  1. round_robin (Default): Distribusi lalu lintas secara bergantian dan berurutan dari backend pertama hingga terakhir (1, 2, 3, 1, 2, 3…). Algoritma ini sangat cocok jika spesifikasi perangkat keras semua backend identik.
  2. least_conn: Caddy memantau jumlah request aktif yang sedang diproses oleh masing-masing backend dan mengarahkan request baru ke backend yang memiliki beban kerja terkecil saat itu. Ini adalah opsi terbaik untuk request dengan durasi proses yang bervariasi.
  3. random: Memilih backend secara acak untuk setiap permintaan masuk.
  4. random_choose <n>: Memilih <n> backend secara acak terlebih dahulu (misal 2 backend), lalu dari kedua backend pilihan tersebut, Caddy memilih yang memiliki koneksi aktif paling sedikit. Teknik ini menyeimbangkan kecepatan keputusan acak dengan keadilan distribusi.
  5. first: Caddy selalu mengirimkan lalu lintas ke backend pertama yang terdaftar. Backend berikutnya hanya akan digunakan jika backend sebelumnya dinyatakan mati (unhealthy). Sangat cocok untuk skenario primary-backup.
  6. ip_hash: Caddy menghitung nilai hash dari alamat IP klien dan menggunakannya untuk menentukan backend. Ini memastikan klien yang sama selalu terhubung ke backend yang sama (sticky session berbasis IP).
  7. uri_hash: Caddy memetakan request berdasarkan URI path. Request ke URL yang sama akan selalu diproses oleh backend yang sama, sangat berguna untuk meningkatkan efisiensi sistem caching lokal backend.
  8. cookie: Caddy menyisipkan cookie pelacak ke browser klien pada request pertama dan menggunakannya untuk menjaga agar sesi klien berikutnya tetap terikat (sticky session) pada instance backend yang sama.
# Contoh sticky session menggunakan cookie
app.example.com {
    reverse_proxy backend-1:3000 backend-2:3000 {
        lb_policy cookie {
            name session_id
            secret "kunci-rahasia-produksi-harus-panjang"
        }
    }
}

Pemantauan Kesehatan (Health Checks) #

Untuk memastikan pengguna tidak mendapatkan halaman error saat salah satu backend mati, Caddy dapat melakukan pemantauan kesehatan secara berkala. Backend yang terdeteksi bermasalah akan segera dikeluarkan dari daftar distribusi lalu lintas.

flowchart TD
    Caddy[Caddy Proxy] -->|Akses Normal| B1[Backend 1: Sehat]
    Caddy -.->|Koneksi Diputus| B2[Backend 2: Mati]
    
    subgraph Healthcheck_Engine[Mesin Healthcheck]
        HC[Poller Aktif] -.->|Ping periodik /health setiap 10s| B2
        HC -->|Backend 2 Gagal 3x| MarkUnhealthy[Tandai Unhealthy]
    end

    style B1 stroke:#43a047,stroke-width:2px
    style B2 stroke:#e53935,stroke-dasharray: 5,5

1. Health Check Aktif (Active Health Checks) #

Pada mekanisme ini, Caddy secara proaktif mengirimkan permintaan HTTP secara berkala ke jalur URL khusus yang disediakan oleh backend untuk memeriksa kesehatannya secara langsung.

# Konfigurasi active health check
app.example.com {
    reverse_proxy backend-1:3000 backend-2:3000 {
        health_uri      /healthz
        health_interval 10s
        health_timeout  5s
        health_status   200
        
        health_headers {
            X-Checker "Caddy-Monitor"
        }
    }
}
  • health_uri: Endpoint backend yang akan dihubungi (biasanya mengembalikan status kesehatan database dan layanan internal backend).
  • health_interval: Jeda waktu antar pemeriksaan (dalam contoh di atas, setiap 10 detik).
  • health_timeout: Batas waktu tunggu respons. Jika backend tidak menjawab dalam 5 detik, pemeriksaan dianggap gagal.
  • health_status: Status HTTP yang diharapkan untuk menyatakan backend sehat (biasanya 200).

2. Health Check Pasif (Passive Health Checks) #

Berbeda dengan aktif, health check pasif bekerja secara diam-diam dengan memantau kegagalan dari transaksi permintaan riil dari pengguna asli.

# Konfigurasi passive health check
app.example.com {
    reverse_proxy backend-1:3000 backend-2:3000 {
        health_fails    3
        fail_duration   30s
        max_fails       3
    }
}

Jika Caddy mendeteksi kegagalan koneksi atau error tingkat transport sebanyak 3 kali (health_fails) berturut-turut dalam kurun waktu 30 detik (fail_duration), backend tersebut akan langsung ditandai sebagai unhealthy dan dinonaktifkan sementara waktu dari penerimaan lalu lintas baru.


Penanganan Kegagalan: Retry dan Circuit Breaker #

Ketika sebuah backend mengalami gangguan sesaat (network hiccup), kita ingin Caddy mencoba mengirimkan kembali permintaan tersebut ke backend cadangan sebelum menyerah dan mengembalikan halaman error ke pengguna.

# Konfigurasi retry durasi
app.example.com {
    reverse_proxy backend-1:3000 backend-2:3000 {
        lb_try_duration 5s
        lb_try_interval 250ms
    }
}
  • lb_try_duration: Menginstruksikan Caddy untuk terus mencoba mengalihkan request ke backend lain yang sehat selama maksimal 5 detik sebelum akhirnya menyerah dan mengembalikan status error 502 Bad Gateway.
  • lb_try_interval: Jeda waktu tunggu antara setiap percobaan pemindahan koneksi (dalam contoh di atas, 250 milidetik).

Siklus Circuit Breaker #

Dengan mengombinasikan health_fails, fail_duration, dan lb_try_duration, Caddy menerapkan pola Circuit Breaker (pemutus sirkuit) otomatis untuk melindungi backend yang sedang kewalahan agar tidak terus-menerus dibombardir oleh request baru:

stateDiagram-v2
    [*] --> Closed: Backend Berjalan Normal
    Closed --> Open: Kegagalan beruntun > max_fails (Circuit Breaker Aktif)
    Note right of Open: Lalu lintas dialihkan ke backend lain
    Open --> HalfOpen: Waktu fail_duration berakhir
    HalfOpen --> Closed: Request uji coba sukses (Backend pulih)
    HalfOpen --> Open: Request uji coba gagal kembali

WebSocket Proxying dan Streaming Data #

Salah satu keunggulan besar Caddy adalah penanganan protokol koneksi persisten seperti WebSocket dan Server-Sent Events (SSE) secara otomatis tanpa memerlukan deklarasi khusus.

# Konfigurasi reverse proxy WebSocket otomatis
chat.example.com {
    # Caddy mendeteksi header 'Upgrade: websocket' secara otomatis
    reverse_proxy localhost:8080
}

Jika aplikasi chat kita menggunakan WebSocket, Caddy mendeteksi keberadaan header jabat tangan Upgrade: websocket dan Connection: Upgrade yang dikirim oleh klien secara otomatis, lalu meningkatkan (upgrade) koneksi TCP tersebut menjadi saluran komunikasi dua arah (full-duplex) yang persisten.

Caching Buffering vs Streaming (SSE) #

Untuk aplikasi yang mengirimkan respons secara bertahap dan terus-menerus (streaming) seperti API Server-Sent Events (SSE) atau pemutaran video dinamis, Caddy secara default akan menampung respons di dalam memori buffer terlebih dahulu sebelum mengirimkannya ke klien untuk efisiensi transfer data.

Namun, perilaku ini akan merusak komunikasi real-time pada layanan SSE. Kita harus menginstruksikan Caddy untuk segera mengirimkan setiap bagian data tanpa ditunda menggunakan parameter flush_interval -1:

# Mengamankan koneksi streaming real-time
stream.example.com {
    reverse_proxy localhost:8000 {
        # Flush data segera ke klien tanpa buffering
        flush_interval -1
    }
}

Penulisan Ulang Jalur (Path Rewriting) #

Dalam arsitektur layanan mikro, server backend sering kali didesain untuk melayani permintaan dari jalur URL dasar (root path /), sementara Caddy memisahkannya berdasarkan awalan nama subdirektori publik (seperti /api/ atau /app/).

flowchart TD
    Client["Klien mengirim: GET /api/v1/users"] -->|"Caddy strip '/api'"| Backend["Backend menerima: GET /v1/users"]

Jika kita langsung meneruskan permintaan tersebut, backend kita akan mengembalikan error 404 Not Found karena tidak mengenali awalan path /api. Kita harus memotong (strip) awalan path tersebut menggunakan kombinasi blok handle dan direktif uri strip_prefix sebelum mengirimkannya ke backend:

# Contoh strip prefix path
example.com {
    # Arahkan semua request berawalan /api ke backend API
    handle /api/* {
        uri strip_prefix /api
        reverse_proxy localhost:8080
    }
    
    # Sisa request lain disajikan oleh frontend web server
    handle {
        root * /var/www/frontend/dist
        file_server
    }
}

Dinamis Upstream (Dynamic Upstreams) #

Pada infrastruktur komputasi awan modern seperti Docker Swarm, Kubernetes, atau HashiCorp Consul, alamat IP server backend berubah secara dinamis setiap kali ada proses deploy ulang atau penskalaan otomatis (autoscaling). Menuliskan alamat IP statis di Caddyfile akan menyulitkan pemeliharaan.

Caddy menyelesaikan masalah ini dengan menyediakan subdirektif dynamic yang memungkinkan penemuan alamat backend (service discovery) secara dinamis melalui kueri DNS secara real-time.

# Menggunakan resolusi DNS dinamis
app.example.com {
    reverse_proxy {
        dynamic a {
            # Kueri record DNS A/AAAA secara dinamis
            name    backend-service.local
            port    3000
            refresh 30s
            
            resolvers 10.0.0.2 1.1.1.1
        }
    }
}
  • dynamic a: Meminta Caddy untuk menanyakan rekaman (record) DNS tipe A atau AAAA dari domain backend-service.local secara berkala.
  • refresh: Mengontrol seberapa sering Caddy harus melakukan kueri ulang DNS untuk memperbarui daftar IP backend (dalam contoh ini, setiap 30 detik).
  • resolvers: Alamat IP DNS server internal jaringan kita yang menyimpan peta nama layanan.

Pola Konfigurasi Produksi yang Lengkap #

Berikut adalah templat konfigurasi Caddyfile untuk kebutuhan lingkungan produksi yang menggabungkan seluruh teknik terbaik: logging terstruktur, kompresi respons dinamis, penyeimbangan beban dengan sticky session, proteksi kegagalan koneksi, pemantauan kesehatan aktif, serta halaman pemeliharaan kustom.

# Global option untuk mengaktifkan debug logging (jika diperlukan)
{
    email [email protected]
}

# Blok konfigurasi situs utama
app.example.com {
    # Aktifkan kompresi data modern
    encode gzip zstd
    
    # Logging akses terstruktur dalam format JSON
    log {
        output file /var/log/caddy/app_access.log {
            roll_size     100mb
            roll_keep     10
            roll_keep_for 720h
        }
        format json
    }
    
    # Reverse proxy ke cluster server backend utama
    reverse_proxy backend-node-1:3000 backend-node-2:3000 backend-node-3:3000 {
        # Algoritma pembagian beban
        lb_policy least_conn
        
        # Penanganan kegagalan transisi koneksi
        lb_try_duration 5s
        lb_try_interval 200ms
        
        # Pemantauan kesehatan aktif ke endpoint backend
        health_uri      /healthz
        health_interval 10s
        health_timeout  3s
        health_status   200
        
        # Pemantauan kesehatan pasif tambahan
        health_fails  3
        fail_duration 30s
        
        # Manipulasi header request untuk keamanan backend
        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}
        
        # Konfigurasi lapisan transport HTTP ke backend
        transport http {
            dial_timeout            3s
            response_header_timeout 15s
        }
    }
    
    # Tangkap error 502/503 jika seluruh backend mati dan sajikan halaman pemeliharaan
    handle_errors {
        @service_down expression {err.status_code} in [502, 503, 504]
        handle @service_down {
            header Content-Type "text/html; charset=utf-8"
            respond <<HTML
            <!DOCTYPE html>
            <html lang="id">
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <title>Server Sedang Pemeliharaan</title>
                <style>
                    body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; text-align: center; padding: 150px; background-color: #f7f9fa; color: #333; }
                    h1 { font-size: 40px; margin-bottom: 10px; color: #e53935; }
                    p { font-size: 20px; color: #666; }
                </style>
            </head>
            <body>
                <h1>Mohon Maaf</h1>
                <p>Layanan kami sedang dalam pemeliharaan berkala atau kapasitas backend sedang penuh. Silakan coba kembali beberapa saat lagi.</p>
            </body>
            </html>
            HTML 503
        }
    }
}

Ringkasan #

  • Sintaks Dasar: Direktif reverse_proxy menyederhanakan konfigurasi gerbang masuk aplikasi backend dengan manajemen koneksi siaga (connection pool) bawaan.
  • Load Balancing: Kebijakan algoritma seperti least_conn sangat direkomendasikan untuk beban kerja nyata karena mendistribusikan lalu lintas berdasarkan kesibukan backend secara dinamis.
  • Health Checks: Caddy mendukung pemantauan kesehatan proaktif (active check via ping endpoint khusus) dan observasi reaktif (passive check via analisis transaksi eror nyata) untuk menyingkirkan backend bermasalah secara dinamis.
  • Failover & Recovery: Dengan menyetel lb_try_duration, kita memberikan toleransi waktu bagi Caddy untuk mengalihkan request ke server cadangan secara transparan sebelum mengembalikan kode status eror ke pengunjung.
  • WebSocket & Streaming: Jaringan WebSocket ditangani secara transparan tanpa konfigurasi manual, sedangkan untuk SSE atau transmisi berkas streaming harus ditambahkan parameter flush_interval -1 untuk mencegah penundaan buffer data.

← Sebelumnya: Konsep Reverse Proxy   Berikutnya: Proxy Headers →

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