Belajar FrankenPHP - Firewall, Rate Limit & DoS Mitigation
Episode 16 of 25

Belajar FrankenPHP - Firewall, Rate Limit & DoS Mitigation

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.

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

Pendahuluan

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.

Directive rate_limit di Caddy

Directive rate_limit (tersedia di Caddy 2.9+, yang menjadi basis FrankenPHP) membatasi request berdasarkan matcher. Sintaks dasar:

Caddyfile - rate_limit global
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.

Matcher: Batasi Hanya Endpoint Tertentu

Rate limit jarang perlu diterapkan seragam. Halaman statis dan gambar tidak masalah; endpoint login, checkout, dan upload butuh batasan ketat. Gunakan matcher @:

Caddyfile - rate limit per endpoint
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.

Custom Response untuk 429

Berikan informasi yang benar saat batas tercapai:

Caddyfile - response 429
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.

Memasukkan Header sebagai Key

Kadang IP tidak cukup — misal API yang memakai API key per klien. Caddy mendukung key berbasis header:

Caddyfile - rate limit per API key
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.

Firewall Level IP: remote_ip Matcher

Untuk allow/deny langsung berdasarkan IP (bukan limit), pakai matcher remote_ip:

Caddyfile - blokir IP mencurigakan
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.

Hardening Dasar Anti-DoS Lainnya

Selain rate limiting, beberapa knob yang bisa dikencangkan:

PengaturanNilai yang disarankanMencegah
request_body_timeout30–60sSlowloris (request lambat)
header_up Connection / header_downsesuai kebutuhanHeader abuse
max_header_sizewajar (misal 8KB)Header besar yang membebani parsing
rate_limit di route statisbatas santai (misal 500/menit/IP)Scraping seluruh situs
Jumlah worker/threadsesuai resource (episode 17)Thread exhaustion

Contoh lengkap pengaturan body:

Caddyfile - body & timeout hardening
kaliansite.com {
    root * public/
 
    php_server {
        request_body_timeout 30s
    }
 
    rate_limit {
        zone general {
            key {remote_host}
            events 300
            window 1m
        }
    }
}

Memantau Efektivitas Rate Limit

Rate limit yang tidak dipantau sama saja dengan tidak ada. Integrasikan dengan observability dari episode 12:

  • Log 429: filter access log untuk status 429 → tahu berapa banyak request yang diblokir per hari.
  • Metrik: tambahkan counters di Prometheus untuk rate_limit_blocked_total.
  • Alerting: jika persentase 429 melonjak drastis, kemungkinan ada serangan terorganisir — atau konfigurasi terlalu ketat.

Grafana query sederhana untuk memantau:

PromQL - request terbatasi per menit
rate(frankenphp_http_responses_total{status="429"}[1m])

Common Pitfalls

  • Rate limit di belakang proxy: {remote_host} selalu IP proxy jika trusted_proxies tidak diset (episode 9) — semua user satu IP, semua kena blokir.
  • Terlalu ketat: 5 request/menit untuk seluruh situs akan memblokir pengguna normal. Bedakan zona per endpoint.
  • Key kosong: jika header X-API-Key hilang, semua klien tanpa key masuk satu bucket — pisahkan dengan key default.
  • Lupa zone default: request yang tidak cocok matcher mana pun tidak terbatasi. Tentukan apakah itu diinginkan.

Penutup

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}).
  • Pisahkan zona per endpoint sensitif (login, checkout) dengan matcher @.
  • Jawab 429 dengan Retry-After; gunakan remote_ip untuk blokir langsung.
  • request_body_timeout dan batas body melindungi dari slow requests.
  • Rate limit adalah lapisan pertama, bukan pengganti CDN/firewall cloud; pantau lewat log dan metrik.

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!

Belajar FrankenPHP - Firewall, Rate Limit & DoS Mitigation | Belajar FrankenPHP