Melindungi aplikasi FrankenPHP dari serangan berbasis volume: rate limit per-IP dengan directive rate_limit, pembatasan endpoint sensitif (login, checkout, upload), konfigurasi matcher, dan langkah-langkah dasar anti-DoS di level server.

Episode 15 menangani kerentanan yang butuh patch; episode ini menangani lawan yang lebih membosankan namun sama mematikannya: volume. Bot scraping, credential stuffing di halaman login, dan ledakan request ke satu endpoint — ketiganya adalah pola yang sama: terlalu banyak request dari satu sumber dalam waktu singkat. FrankenPHP (lewat Caddy 2.9+) punya senjata langsung untuk ini: directive rate_limit.
Mengapa penting? Rate limiting di level server lebih baik daripada di aplikasi: ia bekerja bahkan saat aplikasi kewalahan, tidak menghabiskan resource PHP, dan menerapkan batas untuk semua route sekaligus. Dengan konfigurasi yang tepat, satu Caddyfile sudah memfilter mayoritas serangan volume tanpa menyentuh kode aplikasi.
rate_limit di CaddyDirective rate_limit (tersedia di Caddy 2.9+, yang menjadi basis FrankenPHP) membatasi request berdasarkan matcher. Sintaks dasar:
kaliansite.com {
rate_limit {
zone dynamic {
key {remote_host}
events 50
window 1m
}
}
root * public/
php_server
}Artinya: setiap IP (key {remote_host}) maksimal 50 request per menit. Request ke-51 dijawab 429 Too Many Requests.
Rate limit jarang perlu diterapkan seragam. Halaman statis dan gambar tidak masalah; endpoint login, checkout, dan upload butuh batasan ketat. Gunakan matcher @:
kaliansite.com {
@sensitive path /login /register /api/checkout
rate_limit {
zone login_zone {
match @sensitive
key {remote_host}
events 5
window 1m
}
}
root * public/
php_server
}Skenario: upaya login dari IP yang sama dibatasi 5 kali per menit — tidak menghalangi pengguna normal, tapi membuat credential stuffing jauh lebih lambat.
Berikan informasi yang benar saat batas tercapai:
rate_limit {
zone login_zone {
match @sensitive
key {remote_host}
events 5
window 1m
}
}
respond @login_limit 429 {
body "Terlalu banyak request. Silakan coba lagi nanti."
}Aplikasi juga perlu tahu kapan batas tercapai — header Retry-After memberi klien petunjuk kapan boleh mencoba lagi. Headers yang di-set otomatis oleh Caddy: RateLimit-* dan Retry-After.
Kadang IP tidak cukup — misal API yang memakai API key per klien. Caddy mendukung key berbasis header:
rate_limit {
zone api_zone {
match @api
key {http.request.header.X-API-Key}
events 100
window 1m
}
}Klien yang memakai API key yang sama dibatasi bersama-sama — cocok untuk SaaS backend.
remote_ip MatcherUntuk allow/deny langsung berdasarkan IP (bukan limit), pakai matcher remote_ip:
kaliansite.com {
@blocked remote_ip 203.0.113.7 198.51.100.0/24
respond @blocked 403
root * public/
php_server
}Semua request dari IP/CIDR di daftar dijawab 403 sebelum menyentuh aplikasi. Ini adalah pertahanan pertama yang murah untuk IP yang terbukti menyerang (dari WAF atau firewall cloud).
Warning
Mengandalkan daftar blokir manual tidak akan pernah cukup untuk DDoS skala besar — itu pekerjaan CDN/firewall cloud. remote_ip dan rate_limit adalah lapisan pertama yang baik, bukan pengganti Cloudflare/AWS Shield.
Selain rate limiting, beberapa knob yang bisa dikencangkan:
| Pengaturan | Nilai yang disarankan | Mencegah |
|---|---|---|
request_body_timeout | 30–60s | Slowloris (request lambat) |
header_up Connection / header_down | sesuai kebutuhan | Header abuse |
max_header_size | wajar (misal 8KB) | Header besar yang membebani parsing |
rate_limit di route statis | batas santai (misal 500/menit/IP) | Scraping seluruh situs |
| Jumlah worker/thread | sesuai resource (episode 17) | Thread exhaustion |
Contoh lengkap pengaturan body:
kaliansite.com {
root * public/
php_server {
request_body_timeout 30s
}
rate_limit {
zone general {
key {remote_host}
events 300
window 1m
}
}
}Rate limit yang tidak dipantau sama saja dengan tidak ada. Integrasikan dengan observability dari episode 12:
429 → tahu berapa banyak request yang diblokir per hari.rate_limit_blocked_total.Grafana query sederhana untuk memantau:
rate(frankenphp_http_responses_total{status="429"}[1m]){remote_host} selalu IP proxy jika trusted_proxies tidak diset (episode 9) — semua user satu IP, semua kena blokir.X-API-Key hilang, semua klien tanpa key masuk satu bucket — pisahkan dengan key default.Pada episode 16 ini, kalian telah melindungi aplikasi FrankenPHP dari serangan volume.
Inti yang harus dibawa pulang:
rate_limit membatasi per-IP atau per-header (key {remote_host}, {http.request.header.X-API-Key}).@.Retry-After; gunakan remote_ip untuk blokir langsung.request_body_timeout dan batas body melindungi dari slow requests.Di episode 17 selanjutnya kita masuk bagian yang paling dinanti: performance tuning & benchmark — mengatur num_threads, hubungannya dengan memory limit, opcache, dan cara membandingkan FrankenPHP dengan Nginx+PHP-FPM secara jujur. Sampai jumpa di episode 17!