Skip to content
Konsultasi

Situs Dibilang Kena DDoS, Log Nginx Bilang Lain

Adityo Guni Waluyo

Laporan DDoS berakhir di log: 80 persen request 504 persis tiap jam, limit_req tak pernah terpicu, pelakunya PHP-FPM kehabisan worker.

Ringkasan

Laporan dugaan DDoS ternyata gelombang 504 periodik tiap jam akibat antrean PHP-FPM yang macet, bukan serangan. Rate limit tak pernah terpicu karena $binary_remote_addr menangkap IP Cloudflare, bukan IP asli pengunjung. Solusinya: aktifkan cache Cloudflare, sesuaikan pool PHP-FPM, dan atur logrotate untuk log dalam container.

"Situs saya kena DDoS?" Kalimat itu terlontar dari user report yang masuk. Saya buka monitor: 80 persen request berstatus 504, persis tiap jam 00 menit, berlangsung 10-20 menit lalu sembuh sendiri. Dua puluh menit kemudian muncul lagi. Polanya terlalu rapi untuk serangan yang acak — tapi terlalu menyakitkan untuk diabaikan.

Saya langsung beralih ke akses log. Ternyata lognya DI DALAM container Docker, bukan di /var/log/nginx/ host. File access.log udah 1,0 GB (sekitar 4,1 juta baris), error.log 42 MB, dan logrotate di host jelas nggak mencakup log di dalam container, masalah klasik dunia container seperti yang dulu saya tulis di entrypoint Docker yang memilih interpreter salah [4]. Di situ saya mulai menemukan keanehan pertama.

Membaca Pola di Dalam Log

Saya ambil sampel 24 jam. Dari 106.752 request, 85.329 berstatus 504 (80 persen), 15.749 yang berhasil, dan sisanya 404 dari scanner [4].

Gelombang 502/504 terjadi terlalu tepat waktu untuk serangan acak. Setiap gelombang berlangsung 10-20 menit, dan tepat saat gelombang terjadi, request yang berhasil jatuh mendekati nol. Saya buka error.log, dan setiap gelombang selalu menunjukkan baris yang sama: upstream timed out (110: Connection timed out) ... upstream: fastcgi://[ip-php-fpm]:9000. PHP-FPM nggak mampu merespons sebelum nginx melempar handuk.

Saya juga menemukan jejak scanner: feroxbuster/2.13.1 burst 655 request dalam 3 detik, mencari path seperti .env, .git/config, dan phpinfo.php. Semua berujung 404. Nggak ada satu pun yang berhasil. Itu scanner biasa, bukan penyerang yang menargetkan data sensitif.

Rate limit yang nggak pernah terpicu

Saya pernah pasang limit_req untuk zone LIMITATTACK dengan rate 2 request per detik plus burst 5 nodelay:

limit_req_zone $binary_remote_addr zone=LIMITATTACK rate=2r/s;
limit_req zone=LIMITATTACK burst=5 nodelay;

Di atas kertas, ini terlihat benar. Tapi di error.log saya nggak menemukan satu baris pun "limiting requests". Berarti limit nggak pernah terpicu.

Situs ini berada di belakang Cloudflare. $binary_remote_addr menangkap IP edge Cloudflare, bukan IP pengunjung asli [1]. Cloudflare mengirim IP sebenarnya lewat header CF-Connecting-IP [2], tapi nginx saya nggak membaca header itu untuk zone rate-limit. Akibatnya ribuan pengunjung asli dianggap satu IP yang sama, dan threshold nggak pernah tercapai.

Analisisnya saya pindah ke laptop: sampel log terakhir saya tarik ke mesin lokal sekali, lalu seluruh parsing dilakukan lokal dengan Python, bukan awk bersarang di dalam SSH yang satu tingkat quoting-nya saja udah rawan rusak. Bonusnya, file sampel itu bisa di-query ulang berkali-kali tanpa menyentuh server lagi.

Hal ini sejalan dengan pelajaran dari DNS ISP yang meracuni riset online: berada di balik lapisan intermediari sering membuat diagnosa konvensional nggak akurat.

Tersisa satu tersangka, dan ini yang paling masuk akal: bukan serangan yang membanjiri jaringan, tapi antrean yang mampet di aplikasi. **pm.max_children** di PHP-FPM membatasi jumlah proses anak simultan [3]. Ketika request menumpuk dan worker baru nggak bisa dibuat, antrean memanjang. request_terminate_timeout mematikan worker yang terlalu lama, tapi ini justru memperparah situasi: request yang udah setengah diproses harus dimulai ulang.

Gejalanya sangat khas: error.log konsisten menunjukkan upstream timed out ke fastcgi, gelombang terjadi tiap jam dengan durasi yang hampir sama, dan semuanya sembuh sendiri setelah 10-20 menit. Pemicu lonjakan periodik ini belum sepenuhnya saya identifikasi. Crontab host kosong dan nggak ada jadwal WordPress yang jelas menjelaskan pola ini.

Daripada sibuk memblokir IP yang berganti-ganti, saya fokus ke masalah yang nyata:

  1. Page cache Cloudflare: aktifkan Cache Everything dengan Edge TTL panjang, bypass untuk wp-admin dan cookie login. Situs berita nyaris statis bagi pembaca anonim; request yang ter-cache nggak menyentuh PHP-FPM sama sekali. 2. Pool PHP-FPM: review pm.max_children, request_terminate_timeout, dan fastcgi_read_timeout di nginx agar sesuai dengan RAM server [3]. Ini perlu approval karena menyangkut server produksi. 3. Logrotate untuk container: atur copytruncate untuk log di dalam container supaya nggak membengkak hingga 1 GB. Tanpa ini, masalah kapasitas akan bergulir ke masalah disk penuh [4].

Sekarang, tiap ada laporan "kena DDoS", langkah pertama saya makin murah: tarik sampel log ke mesin lokal, hitung status code per jam, lalu tanya satu hal ke data itu. Apakah polanya acak atau periodik? Kali ini jawabannya periodik, dan itu udah cukup untuk menyelamatkan satu malam tidur. Limit yang ternyata nggak pernah terpicu dan header yang nggak pernah dibaca, dua-duanya baru kelihatan setelah lognya dibaca pelan-pelan [1][2].

Sumber:

[1] https://nginx.org/en/docs/http/ngx_http_limit_req_module.html [2] https://developers.cloudflare.com/fundamentals/reference/http-headers/ [3] https://www.php.net/manual/en/install.fpm.configuration.php [4] https://nginx.org/en/docs/http/ngx_http_log_module.html

Artikel terkait