Lewati ke isi

Keamanan/Cyber Security

Cara Membatasi Request Berlebih dengan Rate Limiting Nginx

Panduan limit_req dan limit_conn di Nginx untuk login dan API: memilih rate, burst dan nodelay, membalas 429, dan menguji tanpa mengunci pengguna.

Terbit 6 menit baca

Jawaban singkat

Rate limiting Nginx memakai limit_req_zone dan limit_req untuk membatasi jumlah request per IP per detik atau menit, serta limit_conn untuk membatasi koneksi bersamaan. Pasang batas ketat di login dan API, beri burst untuk lonjakan wajar, balas 429, dan uji dulu dengan mode dry run.

Daftar isi
  1. Dua alat yang berbeda fungsi
  2. Memilih angka untuk login, API, dan halaman biasa
  3. Apa arti burst dan nodelay?
  4. Membalas 429, bukan 503
  5. Menguji tanpa mengunci pengguna asli
  6. Ringkasnya

Satu percobaan login palsu memang murah, tapi ribuan percobaan per jam ke wp-login.php tetap membuat PHP dan database bekerja untuk setiap request, walaupun tidak ada yang berhasil masuk. Rate limiting memotong beban itu di pintu depan.

Di Nginx, rate limiting adalah aturan bahwa satu klien (biasanya diidentifikasi dari IP) hanya boleh mengirim sejumlah request dalam satu satuan waktu. Kelebihannya ditolak di Nginx, sebelum menyentuh aplikasi. Modul yang dipakai, ngx_http_limit_req_module dan ngx_http_limit_conn_module, sudah termasuk di build Nginx standar.

Dua alat yang berbeda fungsi

limit_req membatasi laju request, misalnya 5 request per menit per IP. Ia memakai algoritma leaky bucket: request diproses dengan kecepatan tetap, kelebihannya ditunda atau ditolak.

limit_conn membatasi jumlah koneksi bersamaan, misalnya maksimal 20 koneksi terbuka per IP. Ini berguna untuk unduhan file besar atau klien yang membuka banyak koneksi sekaligus.

Keduanya butuh dua bagian. Zona memori dideklarasikan di blok http, lalu dipakai di server atau location:

http {
  limit_req_zone  $binary_remote_addr zone=login:10m rate=5r/m;
  limit_req_zone  $binary_remote_addr zone=api:10m   rate=10r/s;
  limit_conn_zone $binary_remote_addr zone=addr:10m;
  limit_req_status  429;
  limit_conn_status 429;
}

$binary_remote_addr dipakai karena ukurannya tetap dan hemat memori. Menurut dokumentasi Nginx, satu status memakan 128 byte di server 64-bit, sehingga zona 1 MB menampung sekitar 8 ribu status (sekitar 16 ribu di platform 32-bit). Zona 10 MB sudah lapang untuk kebanyakan website.

Memilih angka untuk login, API, dan halaman biasa

Angka yang tepat bergantung pada perilaku pengguna aslimu, bukan angka ajaib dari internet. Cara berpikirnya: berapa kali manusia wajar melakukan aksi itu, lalu beri ruang sedikit di atasnya.

Untuk halaman login, manusia jarang mencoba lebih dari beberapa kali dalam semenit. Batas 5 per menit dengan burst kecil sudah memperlambat brute force secara drastis. Untuk API yang dipakai aplikasi mobile sendiri, satu layar aplikasi bisa memanggil beberapa endpoint sekaligus, jadi batas per detik dengan burst lebih besar lebih masuk akal. Halaman biasa sering lebih aman dibiarkan tanpa limit_req, atau dengan batas longgar, karena satu halaman bisa memuat puluhan aset.

AreaContoh rateBurstnodelayCatatan
Login, reset password5r/m3 sampai 5YaPaling penting untuk brute force
Form kontak, komentar2r/m sampai 10r/m2 sampai 5YaKombinasikan dengan CAPTCHA
API aplikasi5r/s sampai 20r/s10 sampai 40YaSesuaikan dengan pola panggilan aplikasi
Unduhan file besar(pakai limit_conn)Misalnya 2 sampai 5 koneksi per IP
Halaman umum dan aset statisBiasanya tidak perluCache dan CDN lebih tepat

Angka di tabel adalah titik awal untuk diuji, bukan standar. Lihat log aksesmu dulu: berapa request per menit yang dikirim pengguna paling aktif di jam ramai?

Penerapannya per lokasi:

location = /wp-login.php {
  limit_req zone=login burst=5 nodelay;
  include fastcgi_params;
  fastcgi_pass unix:/run/php-fpm/www.sock;
}

location /api/ {
  limit_req  zone=api burst=20 nodelay;
  limit_conn addr 20;
  proxy_pass http://127.0.0.1:3000;
}

Apa arti burst dan nodelay?

Tanpa burst, request kedua yang datang lebih cepat dari laju yang diizinkan langsung ditolak. Untuk 5r/m, artinya satu request per 12 detik, dan pengguna yang salah ketik password lalu mencoba lagi segera akan langsung kena tolak. Itu terlalu kejam.

burst=5 menyediakan antrean untuk 5 request tambahan. Secara bawaan, request dalam antrean itu ditunda agar laju keluarnya tetap sesuai rate, sehingga pengguna merasakan halaman lambat. Dengan nodelay, request dalam jatah burst langsung diproses, dan baru request di luar jatah yang ditolak. Untuk kebanyakan kasus web, burst plus nodelay memberi pengalaman paling wajar: lonjakan kecil lolos cepat, serangan beruntun tertahan.

Nginx juga punya parameter delay=N sebagai jalan tengah: N request pertama dalam burst diproses langsung, sisanya ditunda.

Membalas 429, bukan 503

Nilai bawaan limit_req_status adalah 503, yang bagi monitoring dan mesin pencari berarti server sedang bermasalah. Kode 429 (Too Many Requests) lebih jujur: servernya sehat, klien yang terlalu cepat. Itu sebabnya contoh di atas memakai limit_req_status 429.

Kamu bisa menambah halaman galat yang ramah, misalnya error_page 429 /terlalu-cepat.html;, berisi pesan untuk menunggu sebentar. Untuk API, balasan JSON sederhana memudahkan aplikasi menampilkan pesan yang benar.

Menguji tanpa mengunci pengguna asli

Kesalahan paling umum adalah lupa bahwa Nginx di belakang proxy hanya melihat IP proxy. Kalau websitemu di belakang Cloudflare atau load balancer, semua pengunjung tampak datang dari beberapa IP saja, dan rate limiting akan memblokir semua orang sekaligus. Pastikan modul realip dikonfigurasi (set_real_ip_from untuk rentang IP proxy dan real_ip_header yang sesuai) sebelum mengaktifkan batas.

Langkah aman berikutnya adalah limit_req_dry_run on;. Dalam mode ini Nginx tetap menghitung request yang melebihi batas, tapi tidak benar-benar menolaknya. Variabel $limit_req_status akan bernilai REJECTED_DRY_RUN untuk request yang seharusnya ditolak, jadi tambahkan variabel itu ke format log akses. Jalankan beberapa hari, baca lognya, lalu matikan dry run bila tidak ada pengguna sah yang tercatat.

IP kantor atau server monitoring bisa dikecualikan. Nginx tidak menghitung request yang kuncinya kosong, jadi triknya memakai geo dan map:

geo $dikecualikan {
  default 0;
  203.0.113.10 1;
}
map $dikecualikan $kunci_limit {
  0 $binary_remote_addr;
  1 "";
}
limit_req_zone $kunci_limit zone=login:10m rate=5r/m;

Untuk uji cepat, kirim beberapa request beruntun dari terminal dan lihat kode statusnya:

for i in $(seq 1 12); do curl -s -o /dev/null -w "%{http_code}\n" https://contoh.co.id/wp-login.php; done

Kamu seharusnya melihat 200 untuk beberapa request pertama, lalu 429. Selalu jalankan nginx -t sebelum reload.

Rate limiting juga tidak bekerja sendirian. Fail2ban bisa membaca log 429 dan memblokir IP yang terus mengulang di level firewall, seperti dibahas di Fail2ban untuk blokir brute force. Untuk banjir trafik berskala besar yang membanjiri bandwidth, batas di Nginx tidak cukup, dan perlindungannya dibahas di anti-DDoS VPS. Pengamanan dasar server lainnya ada di cara mengamankan VPS baru.

Ringkasnya

Pasang limit_req ketat di login dan form, longgar dengan burst di API, dan biarkan halaman statis diurus cache. Balas dengan 429, pastikan Nginx membaca IP asli pengunjung, dan uji dengan dry run sebelum benar-benar menolak request. Dengan begitu serangan beruntun berhenti di depan pintu, sementara pengguna asli hampir tidak merasakan apa-apa.

Sumber & referensi

Baca juga