Rate Limiting

Rate Limiting #

Rate limiting membatasi jumlah request yang bisa dilakukan oleh klien dalam periode waktu tertentu. Ini adalah lini pertahanan penting terhadap berbagai ancaman: brute force attack pada form login, abuse API oleh bot atau scraper, DDoS layer 7, dan penggunaan berlebihan yang merugikan pengguna lain.

Caddy tidak memiliki rate limiting bawaan — ini adalah keputusan desain yang disengaja agar core tetap ringan. Rate limiting tersedia melalui plugin caddy-ratelimit yang perlu dikompilasi bersama Caddy.

Instalasi Plugin Rate Limit #

# Build Caddy dengan plugin rate limiting
xcaddy build --with github.com/mholt/caddy-ratelimit

# Verifikasi plugin tersedia
./caddy list-modules | grep rate
# Output: http.handlers.rate_limit

# Replace binary yang ada
sudo systemctl stop caddy
sudo cp ./caddy /usr/bin/caddy
sudo systemctl start caddy

Konsep Dasar Rate Limiting #

Window-based rate limiting:
  Tentukan: berapa request yang diizinkan dalam berapa detik?
  
  Contoh: 100 request per menit per IP
  
  Window sliding (lebih smooth):
  T=0s   IP-1 request ke-1
  T=10s  IP-1 request ke-50
  T=59s  IP-1 request ke-100  → limit akan tercapai
  T=60s  IP-1 request ke-101  → 429 Too Many Requests
          (tapi 1 slot terbuka dari request di T=0s)
  
  Window fixed (lebih simple):
  T=0s  - T=60s: IP-1 bisa 100 request
  T=60s - T=120s: IP-1 bisa 100 request lagi (reset)

Konfigurasi Dasar #

example.com {
    rate_limit {
        zone dynamic {
            # Key: apa yang di-track (IP address dalam contoh ini)
            key    {remote_host}
            
            # Window: periode waktu
            window 1m
            
            # Events: jumlah request yang diizinkan dalam window
            events 100
        }
    }
    
    reverse_proxy localhost:3000
}

Rate Limiting per Endpoint #

Endpoint yang berbeda memerlukan limit yang berbeda:

api.example.com {
    # Login endpoint — sangat ketat untuk cegah brute force
    @login path /api/auth/login /api/auth/register
    rate_limit @login {
        zone login_zone {
            key    {remote_host}
            window 15m
            events 10     # Hanya 10 percobaan per 15 menit
        }
    }
    
    # API endpoint umum — lebih longgar
    @api path /api/*
    rate_limit @api {
        zone api_zone {
            key    {remote_host}
            window 1m
            events 60    # 60 request per menit (1 per detik rata-rata)
        }
    }
    
    # Endpoint search — bisa cukup mahal di backend
    @search path /api/search
    rate_limit @search {
        zone search_zone {
            key    {remote_host}
            window 1m
            events 10    # Hanya 10 search per menit
        }
    }
    
    reverse_proxy backend:8080
}

Rate Limiting per User (Setelah Autentikasi) #

Untuk API dengan autentikasi, rate limiting berbasis user lebih adil dari berbasis IP:

api.example.com {
    # Asumsi: backend menambahkan header X-User-ID setelah validasi JWT
    # atau Caddy plugin auth mengekstrak user ID dari token
    
    @authenticated header X-User-ID *
    @unauthenticated not header X-User-ID *
    
    # User terautentikasi: limit lebih longgar
    rate_limit @authenticated {
        zone authenticated_users {
            key    {header.X-User-ID}
            window 1m
            events 1000    # 1000 request per menit per user
        }
    }
    
    # User tidak terautentikasi: limit lebih ketat
    rate_limit @unauthenticated {
        zone public_access {
            key    {remote_host}
            window 1m
            events 30    # Hanya 30 request per menit untuk akses publik
        }
    }
    
    reverse_proxy backend:8080
}

Response Headers Rate Limit #

Memberikan informasi rate limit ke client memungkinkan mereka mengelola request dengan lebih baik:

api.example.com {
    rate_limit {
        zone api {
            key    {remote_host}
            window 1m
            events 100
        }
    }
    
    # Tambahkan header informasi rate limit ke response
    # Plugin rate_limit otomatis menambahkan:
    # X-RateLimit-Limit: 100
    # X-RateLimit-Remaining: 85
    # X-RateLimit-Reset: 1704067200 (unix timestamp)
    # Retry-After: 45 (hanya saat 429)
    
    reverse_proxy backend:8080
}

Client bisa menggunakan header ini untuk menghindari rate limit:

// Client-side rate limit handling
async function apiRequest(url) {
    const response = await fetch(url);
    
    const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
    const reset = parseInt(response.headers.get('X-RateLimit-Reset'));
    
    if (response.status === 429) {
        const retryAfter = parseInt(response.headers.get('Retry-After'));
        console.log(`Rate limited. Retry after ${retryAfter} seconds`);
        await sleep(retryAfter * 1000);
        return apiRequest(url);  // Retry
    }
    
    // Slow down jika hampir mencapai limit
    if (remaining < 10) {
        const timeUntilReset = reset - Math.floor(Date.now() / 1000);
        const delayPerRequest = (timeUntilReset * 1000) / remaining;
        await sleep(delayPerRequest);
    }
    
    return response;
}

Distributed Rate Limiting dengan Redis #

Untuk deployment multi-instance, rate limit perlu di-share antar instance:

{
    # Konfigurasi storage Redis untuk rate limit terdistribusi
}

api.example.com {
    rate_limit {
        distributed {
            # Gunakan Redis sebagai shared storage
            # (tergantung dukungan plugin yang digunakan)
            backend redis {
                host     localhost
                port     6379
                password {env.REDIS_PASSWORD}
            }
        }
        
        zone api_shared {
            key    {remote_host}
            window 1m
            events 100
        }
    }
    
    reverse_proxy backend-1:8080 backend-2:8080 backend-3:8080
}

Rate Limiting di Tingkat Nginx/Caddy vs Backend #

Perbandingan pendekatan:

Di Caddy (layer proxy):
  ✓ Permintaan yang melewati limit TIDAK mencapai backend sama sekali
  ✓ Hemat resource backend
  ✓ Satu titik konfigurasi untuk semua service di balik Caddy
  ✗ Tidak aware konteks aplikasi (user tier, subscription level, dll.)
  ✗ Tidak bisa diferensiasi berdasarkan data yang hanya ada di aplikasi

Di Backend (aplikasi):
  ✓ Bisa rate limit berdasarkan konteks aplikasi (user tier, quota)
  ✓ Bisa custom response message yang lebih informatif
  ✓ Bisa exempt pengguna premium
  ✗ Request tetap sampai ke backend sebelum ditolak (buang resource)
  ✗ Perlu implementasi di setiap service

Rekomendasi: Lakukan KEDUANYA
  → Caddy: rate limit kasar berbasis IP untuk proteksi DDoS/abuse
  → Backend: rate limit halus berbasis user/subscription untuk billing

Rate Limiting Alternatif: Fail2ban #

Jika tidak ingin menggunakan plugin, Fail2ban bisa membaca log Caddy dan memblokir IP yang berulang kali mendapat error:

# Install fail2ban
sudo apt install fail2ban

# Konfigurasi jail untuk Caddy
# /etc/fail2ban/jail.d/caddy.conf
cat << 'EOF' | sudo tee /etc/fail2ban/jail.d/caddy.conf
[caddy-auth]
enabled  = true
port     = http,https
logpath  = /var/log/caddy/access.log
backend  = auto
maxretry = 5
findtime = 600   ; 10 menit
bantime  = 3600  ; 1 jam ban

[caddy-4xx]
enabled  = true
port     = http,https
logpath  = /var/log/caddy/access.log
backend  = auto
maxretry = 50
findtime = 60
bantime  = 1800
EOF

# Filter untuk mendeteksi banyak 4xx dari IP yang sama
# /etc/fail2ban/filter.d/caddy-4xx.conf
cat << 'EOF' | sudo tee /etc/fail2ban/filter.d/caddy-4xx.conf
[Definition]
failregex = ^.*"remote_addr":"<HOST>:[0-9]+".*"status":(4[0-9]{2}),.*$
ignoreregex =
datepattern = %%Y-%%m-%%dT%%H:%%M:%%S
EOF

sudo systemctl restart fail2ban
sudo fail2ban-client status caddy-4xx

Throttling Upload dan Download Besar #

Untuk melindungi bandwidth dan server dari client yang mengunduh/mengunggah secara berlebihan:

files.example.com {
    # Rate limit untuk endpoint upload
    @upload path /upload/*
    rate_limit @upload {
        zone upload_zone {
            key    {remote_host}
            window 1h
            events 20    # Maksimal 20 upload per jam per IP
        }
    }
    
    # Rate limit untuk download file besar
    @download path /files/*
    rate_limit @download {
        zone download_zone {
            key    {remote_host}
            window 10m
            events 50    # 50 file download per 10 menit
        }
    }
    
    file_server { root /var/www/files }
}

Ini penting untuk mencegah satu user menghabiskan bandwidth server, yang pada akhirnya mempengaruhi user lain.


Ringkasan #

  • Rate limiting bawaan Caddy tidak ada — gunakan plugin caddy-ratelimit (xcaddy build --with github.com/mholt/caddy-ratelimit).
  • Buat zona rate limit berbeda untuk endpoint yang berbeda: endpoint login/register jauh lebih ketat dari endpoint API umum.
  • Rate limit berbasis user ID lebih adil dari berbasis IP untuk API dengan autentikasi — pengguna di belakang NAT tidak saling mengganggu kuota.
  • Tambahkan X-RateLimit-* headers ke response agar client bisa mengelola request mereka sendiri dan tidak buang-buang percobaan.
  • Untuk deployment multi-instance, rate limit harus distributed via Redis agar semua instance berbagi state yang sama.
  • Kombinasikan rate limiting di Caddy (proteksi DDoS/abuse kasar) dengan rate limiting di backend (quota per user/subscription) untuk perlindungan berlapis.

← Sebelumnya: Basic Auth   Berikutnya: Pembatasan IP →

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