Belajar FrankenPHP - Reverse Proxy & Load Balancing
Episode 9 of 25

Belajar FrankenPHP - Reverse Proxy & Load Balancing

Menggunakan FrankenPHP sebagai reverse proxy dan load balancer: meneruskan request ke beberapa backend API atau microservices dengan directive reverse_proxy, konfigurasi health check, policy load balancing round_robin / least_conn / random, retry policy, dan pengaturan trusted_proxies.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Sejauh ini FrankenPHP kita gunakan untuk melayani aplikasi PHP secara langsung. Tetapi di dunia nyata, sebuah domain biasanya juga harus melayani API lain, microservices, atau backend yang bukan PHP. Episode ini membahas kemampuan FrankenPHP (melalui Caddy) sebagai reverse proxy dan load balancer — salah satu alasan mengapa satu binary FrankenPHP bisa menggantikan tumpukan "Nginx + PHP-FPM + API gateway" sekaligus.

Mengapa penting? Arsitektur modern hampir selalu memecah aplikasi: frontend PHP, API service, service upload, dsb. Dengan reverse_proxy, satu entry point FrankenPHP bisa merutekan semua itu, mengeraskan TLS, dan menyeimbangkan beban — tanpa infrastruktur tambahan.

Konsep Dasar reverse_proxy

Directive reverse_proxy meneruskan request ke upstream. Contoh paling sederhana — PHP dilayani FrankenPHP, API di backend terpisah:

Caddyfile - reverse proxy dasar
localhost {
    @api path /api/*
    reverse_proxy @api 127.0.0.1:8081
 
    root * public/
    php_server
}

Semua request /api/* diteruskan ke 127.0.0.1:8081; sisanya ditangani PHP. Response dari backend mengalir balik tanpa modifikasi signifikan.

Load Balancing: Beberapa Upstream

Ketika satu backend tidak cukup, daftarkan beberapa upstream dalam blok:

Caddyfile - load balancing
localhost {
    @api path /api/*
    reverse_proxy @api {
        to 10.0.0.11:8081
           10.0.0.12:8081
           10.0.0.13:8081
 
        lb_policy round_robin
    }
 
    root * public/
    php_server
}

Policy Load Balancing

PolicyPerilakuCocok untuk
round_robinGiliran merata ke tiap backendBackend seragam (default)
least_connKirim ke backend dengan koneksi paling sedikitBackend dengan request berat bervariasi
randomPilih acak, boleh dengan choose 2 (2 kandidat)Kecepatan murni

Contoh least_conn:

Caddyfile - least_conn
reverse_proxy @api {
    to 10.0.0.11:8081 10.0.0.12:8081
    lb_policy least_conn
}

Health Check

Backend yang mati tidak boleh menerima request. Caddy melakukan health check aktif (active probe) dan pasif (menandai backend bermasalah setelah gagal N request):

Caddyfile - health check
reverse_proxy @api {
    to 10.0.0.11:8081 10.0.0.12:8081
 
    health_uri /healthz
    health_interval 10s
    health_timeout 3s
 
    max_fails 3
    fail_duration 30s
}
  • health_uri /healthz — Caddy mengecek GET /healthz tiap health_interval.
  • health_interval/health_timeout — frekuensi dan batas waktu probe.
  • max_fails 3 + fail_duration 30s — jika 3 request gagal dalam 30 detik, backend dianggap mati untuk sementara.

Tip

Pastikan aplikasi kalian punya endpoint health (/healthz, /livez, /readyz) yang menjawab cepat. Health check yang lambat akan mengubah load balancer menjadi bottleneck.

Retry Policy

Ketika upstream pertama gagal, Caddy bisa mencoba upstream berikutnya secara otomatis:

Caddyfile - retry
reverse_proxy @api {
    to 10.0.0.11:8081 10.0.0.12:8081
 
    lb_retries 2
    lb_retry_duration 5s
}

lb_retries = jumlah percobaan ke upstream lain; lb_retry_duration = batas waktu total retry. Default Caddy melakukan retry ke upstream berikutnya untuk request idempotent (GET/HEAD) — dengan kedua opsi ini kalian mengontrol kedalaman dan durasinya.

Warning

Jangan retry request yang tidak idempotent (POST yang menulis data) ke backend lain secara membabi buta — request mungkin sudah dieksekusi di backend pertama, lalu dieksekusi lagi di backend kedua (double submit). Pisahkan aturan proxy untuk mutasi dan baca, atau batasi retry.

Mengubah Header di Perjalanan

Proxy sering perlu menambahkan atau menghapus header. Gunakan blok header_up (ke backend) dan header_down (ke klien):

Caddyfile - manipulasi header
reverse_proxy @api {
    to 10.0.0.11:8081
 
    header_up X-Real-IP {remote_host}
    header_up Host api.internal.local
 
    header_down Server ""      # hapus header Server dari backend
    header_down X-Powered-By "" 
}

Ini berguna untuk menyembunyikan identitas backend dan memberikan informasi yang benar ke log aplikasi.

trusted_proxies: Saat FrankenPHP di Belakang Proxy Lain

Jika FrankenPHP sendiri berada di belakang load balancer cloud (AWS ELB, Google Cloud LB, Nginx), header X-Forwarded-* dari proxy tersebut harus dipercaya — jika tidak, IP klien yang terlihat salah dan deteksi HTTPS bisa gagal:

Caddyfile - trusted proxies
{
    servers {
        trusted_proxies static 10.0.0.0/8
    }
}

Sesuaikan CIDR dengan jangkauan IP proxy kalian. Dan jangan lupa sisi aplikasi: Symfony memakai env TRUSTED_PROXIES, Laravel memakai middleware TrustProxies — dua-duanya harus sinkron agar X-Forwarded-Proto/X-Forwarded-For benar.

Common Pitfalls

  • Loop proxy: hati-hati saat mem-proxy request ke hostname yang sama dengan site block — bisa terjadi infinite loop. Gunakan IP internal/upstream langsung.
  • Backend tanpa health endpoint: tanpa health check, backend mati baru ketahuan saat request mulai 502.
  • Retry non-idempotent: double submit pada POST (lihat callout di atas).
  • Lupa trusted_proxies: IP klien selalu tercatat sebagai IP load balancer.

Penutup

Pada episode 9 ini, kalian telah menguasai reverse proxy dan load balancing di FrankenPHP.

Inti yang harus dibawa pulang:

  • reverse_proxy merutekan request ke upstream — API, microservices, backend lain.
  • Load balancing: round_robin, least_conn, random sesuai karakter beban.
  • Health check (health_uri, health_interval) + max_fails menjauhkan backend mati.
  • lb_retries untuk failover; hati-hati dengan request non-idempotent.
  • trusted_proxies wajib saat FrankenPHP berada di belakang proxy cloud.

Di episode 10 selanjutnya kita membahas fitur paling ikonik Caddy: TLS/HTTPS otomatis — Let's Encrypt untuk domain produksi, sertifikat internal untuk localhost, directive tls, dan penyelesaian masalah 127.0.0.1 di Docker. Sampai jumpa di episode 10!