Rate Limiting #

Di internet terbuka, server web kita terus-menerus menghadapi ancaman penyalahgunaan (abuse)—mulai dari serangan menebak kata sandi secara berulang (brute force), pemindaian bot pencari data (scrapers), hingga serangan penolakan layanan terdistribusi (DDoS Layer 7). Rate Limiting adalah lini pertahanan terpenting yang membatasi jumlah permintaan (request) yang dapat dilakukan oleh satu klien dalam jendela waktu tertentu. Dengan membatasi frekuensi lalu lintas masuk, kita tidak hanya mengamankan integritas server dari kelumpuhan, melainkan juga menjaga keadilan pembagian sumber daya bagi seluruh pengguna yang sah. Kita akan membahas secara mendalam berbagai algoritma rate limiting, kompilasi Caddy dengan plugin caddy-ratelimit, konfigurasi batas akses dinamis per-endpoint dan per-user, sinkronisasi klaster terdistribusi menggunakan Redis, serta alternatif integrasi pertahanan menggunakan utilitas Fail2ban.


Urgensi dan Berbagai Algoritma Rate Limiting #

Sebelum kita mengonfigurasi Caddy, kita harus memahami mengapa rate limiting sangat krusial dan bagaimana ia bekerja pada tingkat logis. Rate limiting bertindak sebagai saringan pada gerbang masuk aplikasi. Tanpa rate limiting, penyerang dapat membanjiri database kita dengan kueri pencarian yang berat, melumpuhkan CPU server dalam hitungan detik, dan meningkatkan biaya infrastruktur awan (cloud billing) kita secara drastis.

Ada tiga algoritma utama yang umum digunakan oleh sistem rate limiting untuk melacak dan membatasi request:

AlgoritmaCara Kerja UtamaKelebihanKekurangan
Token BucketServer menyimpan wadah (bucket) berisi token. Setiap request memakan 1 token. Token diisi ulang secara konstan pada laju tertentu.Mendukung lonjakan request (bursting) yang aman.Sedikit lebih kompleks untuk melacak kapasitas token secara real-time.
Leaky BucketRequest masuk ke dalam wadah berlubang. Request diproses pada laju tetap yang keluar dari lubang wadah. Jika wadah penuh, request baru dibuang.Menjamin kestabilan aliran lalu lintas ke backend.Dapat melambatkan respons untuk pengguna yang sah jika antrean penuh.
Sliding WindowPelacakan request menggunakan jendela waktu bergerak yang dinamis (rolling window). Menghitung request dalam rentang waktu aktual terakhir.Sangat akurat dan tidak memiliki masalah lonjakan di batas waktu reset.Membutuhkan konsumsi memori lebih tinggi untuk melacak timestamp request.

Plugin rate limiting Caddy menggunakan variasi Sliding Window untuk menghitung batasan secara akurat, memastikan tidak ada pengguna yang dapat melakukan eksploitasi tepat pada batas reset waktu.


Instalasi Plugin caddy-ratelimit #

Caddy dirancang dengan filosofi inti yang ramping. Oleh karena itu, fitur rate limiting tidak dimasukkan ke dalam paket distribusi standar Caddy. Untuk mengaktifkannya, kita harus menggunakan plugin pihak ketiga yang dikelola oleh komunitas, yaitu caddy-ratelimit (dibuat oleh Matt Holt, pembuat Caddy).

Kita dapat mengompilasi ulang binary Caddy dengan menyertakan plugin ini menggunakan utilitas xcaddy:

# 1. Kompilasi Caddy dengan plugin ratelimit menggunakan xcaddy
xcaddy build --with github.com/mholt/caddy-ratelimit

# 2. Verifikasi hasil build untuk memastikan modul ratelimit telah terpasang
./caddy list-modules | grep rate
# Output yang diharapkan: http.handlers.rate_limit

# 3. Ganti binary Caddy sistem dengan binary baru yang telah dikompilasi
sudo systemctl stop caddy
sudo cp ./caddy /usr/bin/caddy
sudo systemctl start caddy

Konfigurasi Dasar Direktif rate_limit #

Setelah Caddy dikompilasi ulang dengan modul rate limit, direktif rate_limit akan otomatis tersedia untuk digunakan di dalam berkas Caddyfile kita.

Berikut adalah konfigurasi dasar untuk membatasi request per alamat IP klien:

# Konfigurasi dasar pembatasan lalu lintas
example.com {
    rate_limit {
        # Definisikan zona pelacakan (zone)
        zone dynamic_access {
            # Kunci pelacakan (key): Identitas klien (IP Address)
            key {remote_host}
            
            # Rentang waktu jendela pelacakan (sliding window)
            window 1m
            
            # Jumlah request maksimal yang diizinkan dalam window
            events 100
        }
    }
    
    reverse_proxy localhost:8080
}

Dalam konfigurasi di atas:

  • zone: Mendefinisikan nama zona memori internal Caddy untuk melacak status request. Nama zona harus unik di seluruh konfigurasi.
  • key: Kunci identifikasi unik klien. Placeholder {remote_host} menginstruksikan Caddy untuk menggunakan alamat IP klien.
  • window: Mengontrol rentang waktu pelacakan (misalnya 1m untuk 1 menit, atau 15m untuk 15 menit).
  • events: Jumlah maksimal permintaan yang boleh masuk dalam rentang window.

Jika klien mengirimkan request ke-101 dalam waktu kurang dari 1 menit, Caddy akan memblokir request tersebut dan langsung mengembalikan kode status 429 Too Many Requests tanpa meneruskannya ke backend kita.


Rate Limiting per Endpoint secara Spesifik #

Di lingkungan produksi, kebutuhan rate limiting untuk setiap halaman atau rute (path) aplikasi sangat bervariasi. Jalur untuk login membutuhkan keamanan yang sangat ketat untuk mencegah serangan brute force, sedangkan jalur API pencarian membutuhkan batasan sedang karena kueri pencarian database memakan banyak sumber daya CPU.

Kita dapat memadukan named matchers dengan direktif rate_limit untuk menyusun kebijakan pertahanan bertingkat:

# Kebijakan pertahanan bertingkat per-endpoint
api.example.com {
    # ── ZONE 1: Proteksi Endpoint Autentikasi (Sangat Ketat) ──
    @auth_paths path /api/auth/login /api/auth/register /api/auth/forgot-password
    rate_limit @auth_paths {
        zone auth_lock {
            key {remote_host}
            window 15m
            events 5 # Maksimal 5 kali percobaan login per 15 menit
        }
    }

    # ── ZONE 2: Proteksi API Pencarian (Sedang) ──
    @search_path path /api/v1/search
    rate_limit @search_path {
        zone search_lock {
            key {remote_host}
            window 1m
            events 10 # Maksimal 10 kali kueri pencarian per menit
        }
    }

    # ── ZONE 3: Batasan API Umum (Longgar) ──
    @general_api path /api/v1/*
    rate_limit @general_api {
        zone api_lock {
            key {remote_host}
            window 1m
            events 60 # Maksimal 60 request per menit (rata-rata 1 per detik)
        }
    }

    reverse_proxy backend-server:3000
}

Melalui konfigurasi di atas, kita mengunci area-area yang rentan dari eksploitasi secara efisien tanpa mengganggu jalannya transaksi API normal untuk pengguna lainnya.


Rate Limiting Berbasis Identitas Pengguna (User-Authenticated Limiting) #

Membatasi akses hanya berdasarkan alamat IP klien ({remote_host}) memiliki kelemahan kritis ketika aplikasi kita melayani ribuan pengguna yang berada di belakang satu jaringan NAT bersama—seperti jaringan internet kantor, kafe, atau ISP seluler. Di bawah NAT, semua pengguna tersebut akan terlihat memiliki alamat IP publik yang sama oleh Caddy.

Jika satu pengguna melakukan abuse dan memicu limit, seluruh pengguna lain di kantor tersebut akan ikut terblokir secara tidak adil (false positive).

Untuk mengatasinya, jika aplikasi kita menggunakan sistem login, kita dapat membatasi akses berdasarkan User ID atau API Key yang dikirimkan klien setelah berhasil melewati autentikasi:

# Rate limiting berdasarkan identitas pengguna
api.example.com {
    # Asumsikan backend kita menyisipkan header X-User-ID setelah memvalidasi JWT
    @pengguna_auth header X-User-ID *
    @pengguna_anon not header X-User-ID *

    # 1. Kebijakan untuk pengguna yang sudah login (Lebih Longgar)
    rate_limit @pengguna_auth {
        zone user_profile {
            # Kunci pelacakan menggunakan User ID unik dari header
            key {header.X-User-ID}
            window 1m
            events 1000 # Mengizinkan hingga 1000 request per menit
        }
    }

    # 2. Kebijakan untuk tamu anonim (Ketat)
    rate_limit @pengguna_anon {
        zone guest_profile {
            # Kembali menggunakan IP Address sebagai kunci
            key {remote_host}
            window 1m
            events 30 # Hanya 30 request per menit untuk anonim
        }
    }

    reverse_proxy backend:8080
}

Metode ini memastikan bahwa jika pengguna anonim melakukan serangan brute-force, pengguna VIP kita yang terautentikasi tetap dapat menggunakan aplikasi secara lancar tanpa hambatan.


Penanganan HTTP Response Headers Rate Limit #

Memberitahu klien mengenai sisa kuota request mereka adalah praktik terbaik (best practice) dalam perancangan REST API. Hal ini membantu tim frontend untuk mengelola antrean request mereka secara mandiri dan menghindari terjadinya kesalahan HTTP 429.

Secara otomatis, plugin caddy-ratelimit menyertakan header respons standar berikut pada setiap request:

  • X-RateLimit-Limit: Jumlah total request yang diizinkan dalam jendela waktu.
  • X-RateLimit-Remaining: Sisa kuota request yang dapat dilakukan sebelum terblokir.
  • X-RateLimit-Reset: Waktu Unix timestamp kapan kuota request akan di-reset kembali ke awal.
  • Retry-After: (Hanya dikirim saat status HTTP 429 dipicu) Jeda waktu tunggu dalam satuan detik sebelum klien boleh mencoba mengirim request kembali.

Kode Penanganan Sisi Klien (Client-Side Graceful Throttling) #

Berikut adalah contoh fungsi pemanggilan API menggunakan JavaScript di sisi klien yang membaca header rate limit Caddy untuk memperlambat request secara otomatis sebelum limit tercapai:

// client-api.js
// Penanganan rate limit secara dinamis di sisi frontend

async function requestDenganThrottling(url, percobaan = 1) {
    try {
        const response = await fetch(url, {
            headers: {
                'Authorization': 'Bearer token-jwt-kita'
            }
        });

        // 1. Membaca header rate limit dari Caddy
        const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
        const resetTime = parseInt(response.headers.get('X-RateLimit-Reset'));

        // 2. Tangani kasus terblokir (HTTP 429) secara anggun
        if (response.status === 429) {
            const retryAfter = parseInt(response.headers.get('Retry-After')) || 5;
            console.warn(`✗ Terkena Rate Limit Caddy. Menunggu ${retryAfter} detik sebelum mencoba kembali...`);
            
            // Jeda eksekusi selama durasi Retry-After
            await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
            return requestDenganThrottling(url, percobaan + 1); // Coba kembali
        }

        // 3. Mitigasi Proaktif: Memperlambat request jika kuota menipis (< 10 request tersisa)
        if (remaining < 10 && remaining > 0) {
            const sekarang = Math.floor(Date.now() / 1000);
            const jedaReset = resetTime - sekarang;
            
            // Hitung jeda waktu optimal yang disebar di antara sisa kuota
            const delay = (jedaReset * 1000) / remaining;
            console.log(`! Kuota kritis (${remaining} request tersisa). Memperlambat request selama ${delay.toFixed(0)}ms.`);
            await new Promise(resolve => setTimeout(resolve, delay));
        }

        return await response.json();
    } catch (error) {
        console.error("Gagal melakukan panggilan API:", error);
    }
}

// Simulasi eksekusi request berturut-turut
for (let i = 0; i < 20; i++) {
    requestDenganThrottling('https://api.example.com/v1/data');
}

Distributed Rate Limiting Menggunakan Redis #

Ketika infrastruktur kita berkembang menggunakan beberapa instance server Caddy yang berjalan di belakang Load Balancer eksternal (klaster multi-node), melacak rate limit di memori lokal masing-masing server Caddy akan menyebabkan data tidak konsisten.

Klien dapat melewati batas limit dengan cara mengirimkan request secara tersebar ke server Caddy yang berbeda (limit bypass).

Untuk klaster produksi, kita harus mengonfigurasi penyimpanan terdistribusi (distributed storage) menggunakan Redis. Dengan Redis, seluruh instance Caddy akan membaca dan memperbarui status counter rate limit dari satu memori terpusat yang sama:

# Konfigurasi klaster dengan Redis Storage untuk Rate Limiting terdistribusi
example.com {
    rate_limit {
        # Mengonfigurasi Redis sebagai backend penyimpanan state
        # (Modul didukung oleh variasi plugin caddy-ratelimit terdistribusi)
        storage redis {
            address  "redis-cluster.internal:6379"
            username "caddy-node"
            password {env.REDIS_PASSWORD}
            db       0
            timeout  5s
        }

        zone cluster_api {
            key    {remote_host}
            window 1m
            events 100
        }
    }

    reverse_proxy node-1:3000 node-2:3000
}

Perbandingan Taktis: Rate Limiting di Caddy vs Backend #

Dalam merancang arsitektur aplikasi, kita sering dihadapkan pada pertanyaan apakah sebaiknya memasang rate limit di sisi web server (Caddy) atau di sisi kode aplikasi backend (seperti Laravel, Node.js, atau Django).

Berikut adalah analisis trade-off komparatif untuk membantu kita menentukan arsitektur terbaik:

Rate Limiting di Caddy (Layer Proxy):
  ✓ Sangat Ringan: Request diblokir langsung di tepi jaringan sebelum mencapai backend.
  ✓ Perlindungan DDoS Maksimal: Melindungi resource CPU, RAM, dan thread backend dari banjir request.
  ✓ Pengelolaan Terpusat: Satu konfigurasi Caddyfile mengamankan seluruh service di belakangnya.
  ✗ Keterbatasan Konteks: Tidak dapat membedakan kuota detail berdasarkan sisa saldo pulsa atau paket langganan user.

Rate Limiting di Aplikasi Backend:
  ✓ Kontekstual Tinggi: Dapat membatasi akses berdasarkan peran, kuota bulanan, atau tier langganan.
  ✓ Fleksibilitas Respons: Dapat merender halaman error kustom atau melakukan redirect ke halaman upgrade.
  ✗ Pemborosan Resource: Request jahat tetap masuk membebani server web, framework, dan koneksi database backend.

Rekomendasi Pendekatan Hibrida (Hybrid Approach) #

Untuk keamanan tingkat produksi, kita sangat direkomendasikan menerapkan pendekatan hibrida:

  1. Lapis Pertama (Di Caddy): Setel rate limit kasar (coarse-grained) berbasis IP Address dengan batas yang cukup longgar (misalnya 120 request per menit) untuk menangkap serangan spam bot, scraper kasar, dan DDoS Layer 7 sebelum membebani aplikasi.
  2. Lapis Kedua (Di Backend): Setel rate limit halus (fine-grained) berbasis User ID/Token (misalnya kuota 10.000 request per bulan untuk paket Free) di tingkat aplikasi untuk urusan bisnis dan billing.

Alternatif Tanpa Plugin: Integrasi Fail2ban #

Jika kita tidak ingin mengompilasi ulang Caddy dengan plugin eksternal, kita dapat membangun perlindungan abuse menggunakan Fail2ban pada sistem operasi Linux. Fail2ban bekerja dengan cara memantau berkas log akses Caddy secara berkala, dan secara otomatis memblokir alamat IP yang melakukan pelanggaran di tingkat firewall sistem operasi (menggunakan iptables atau nftables).

Berikut adalah langkah-langkah detail konfigurasi integrasi Fail2ban dengan Caddy:

1. Buat Berkas Log Akses JSON pada Caddyfile #

# Mengaktifkan log akses JSON untuk dibaca oleh Fail2ban
example.com {
    log {
        output file /var/log/caddy/access.log
        format json
    }
    
    reverse_proxy localhost:8080
}

2. Konfigurasi Penjara Fail2ban (Jail Configuration) #

Buat berkas konfigurasi baru /etc/fail2ban/jail.d/caddy.conf:

# /etc/fail2ban/jail.d/caddy.conf

[caddy-bad-requests]
enabled  = true
port     = http,https
logpath  = /var/log/caddy/access.log
backend  = auto
# Blokir IP jika memicu 20 kali status error 4xx/5xx dalam waktu 1 menit
maxretry = 20
findtime = 60
# Durasi pemblokiran IP selama 1 jam
bantime  = 3600
filter   = caddy-abuse-detector

3. Buat Berkas Filter Regex Fail2ban #

Buat berkas filter baru /etc/fail2ban/filter.d/caddy-abuse-detector.conf untuk mendeteksi status respons HTTP 4xx atau 5xx dari format JSON log Caddy:

# /etc/fail2ban/filter.d/caddy-abuse-detector.conf

[Definition]
# Regex untuk mengekstrak remote_addr dan status error dari JSON log Caddy
failregex = ^.*"remote_addr":"<HOST>:[0-9]+".*"status":(4[0-9]{2}|5[0-9]{2}),.*$
ignoreregex =
# Menggunakan pola penanggalan ISO8601 bawaan Caddy
datepattern = %%Y-%%m-%%dT%%H:%%M:%%S

Jalankan ulang layanan Fail2ban untuk menerapkan aturan pertahanan kita:

# Restart fail2ban untuk menerapkan konfigurasi jail baru
sudo systemctl restart fail2ban

# Memeriksa status pemblokiran IP
sudo fail2ban-client status caddy-bad-requests

Pembatasan Kecepatan Unggah dan Unduh (Bandwidth Throttling) #

Selain membatasi jumlah events request, rate limiting juga dapat diterapkan untuk melindungi lebar pita (bandwidth) jaringan server dari eksploitasi pengunggahan atau pengunduhan berkas raksasa oleh bot:

# Melindungi bandwidth server file
files.example.com {
    # ── ZONE 1: Batasi Upload Berkas ──
    @upload_route path /api/v1/upload/*
    rate_limit @upload_route {
        zone upload_limit {
            key {remote_host}
            window 1h
            # Hanya izinkan maksimal 5 request unggah berkas per jam
            events 5
        }
    }

    # ── ZONE 2: Batasi Download Berkas Besar ──
    @download_route path /assets/videos/*
    rate_limit @download_route {
        zone video_limit {
            key {remote_host}
            window 10m
            # Hanya izinkan 20 kali download video per 10 menit
            events 20
        }
    }

    file_server {
        root /var/www/shared-files
    }
}

Ringkasan #

  • Kompilasi Plugin: Fitur rate limiting bawaan tidak ada di core Caddy. Kita wajib membangun binary Caddy kustom menggunakan utilitas xcaddy dengan modul caddy-ratelimit.
  • Logika Zona Pelacakan: Konfigurasi rate limiting diatur melalui blok rate_limit dengan menentukan properti zone, key pelacak, window waktu, dan batas events.
  • Kebijakan Bertingkat: Rancang pertahanan bertingkat menggunakan named matchers untuk memberikan limitasi sangat ketat pada endpoint login guna mencegah brute force.
  • Bypass Jaringan NAT: Gunakan pembatasan berbasis identitas User ID (dari token header) alih-alih IP Address agar kuota akses pengguna di belakang NAT bersama tidak saling terganggu.
  • Header Informatif: Caddy secara otomatis menyertakan header X-RateLimit-* dan Retry-After pada respons HTTP untuk memandu sisi frontend mengelola laju panggilan.
  • Klaster Terdistribusi: Di lingkungan multi-server Caddy, hubungkan modul rate limit dengan Redis untuk menyinkronkan counter kuota klien secara real-time.
  • Pertahanan Tepi Jaringan: Rate limiting pada tingkat Caddy (proxy layer) sangat direkomendasikan untuk menolak request jahat secara efisien sebelum sempat membebani database backend.
  • Alternatif Fail2ban: Fail2ban dapat digunakan sebagai solusi pemblokiran IP tingkat firewall sistem operasi dengan mem-parsing berkas log akses JSON Caddy.

← Sebelumnya: Basic Auth   Berikutnya: Pembatasan IP →

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