FUTIA
GÜVENLIK9 dk okuma

fail2ban ile WordPress Brute Force Koruması: Jail, Filter ve Test

WordPress'e dakikada 300 login denemesi yapan bir IP'yi 10 saniyede nasıl engellerim? fail2ban jail dosyası, özel filter ve test komutlarıyla adım adım.

fail2ban ile WordPress Brute Force Koruması: Jail, Filter ve Test
Miraç Eroğlu
1 Mayıs 2026

Geçen hafta futia.net'e dakikada 300'ün üzerinde login denemesi yapan bir IP gördüm. Cloudflare'in altından sızan bu trafik, sunucunun CPU'sunu %80'e çıkarmıştı. Wordfence, Sucuri gibi WordPress eklentileri bu noktada yetersiz kalıyor çünkü saldırı PHP seviyesine ulaşmadan önce engellenmesi gerekiyor. fail2ban burada devreye giriyor: sistem seviyesinde, wp-login.php'ye ulaşmadan önce IP'leri iptables'a ekleyerek trafiği kesiyor. Bu yazıda jail dosyası oluşturmadan, özel filter yazmadan ve test etmeden geçmeyeceğiz. 6 yıldır sosyal medya pazarlama yaparken gördüğüm en büyük güvenlik açığı, sunucu seviyesinde koruma eksikliği. WordPress sitelerinin %90'ı hala uygulama katmanında savunma yapıyor, oysa saldırı HTTP isteği olarak geldiği anda durdurulmalı.

fail2ban Nedir ve WordPress İçin Neden Kritik?

fail2ban, log dosyalarını izleyen ve belirli pattern'lere göre IP'leri otomatik engelleyen bir Python uygulaması. WordPress için kritik çünkü wp-login.php ve xmlrpc.php gibi endpoint'ler sürekli hedef alınıyor. Bir botnet dakikada binlerce istek gönderdiğinde, Wordfence gibi eklentiler her isteği PHP seviyesinde işlemek zorunda kalıyor. Bu da CPU ve RAM tüketimini katlanarak artırıyor.

Ben futia.net'i kurduktan 3 gün sonra ilk brute force saldırısını aldım. Sunucu loglarında aynı IP'den saniyede 5 istek vardı, hepsi POST /wp-login.php. Wordfence 10 dakika sonra IP'yi engelledi ama o 10 dakikada 3000 istek işlenmişti. fail2ban'ı kurduktan sonra aynı IP 5 başarısız denemede banlandı, toplam 15 saniye sürdü.

Önemli fark şu: fail2ban iptables seviyesinde çalışıyor. Yani engellenen IP, sunucuya TCP bağlantısı bile kuramıyor. WordPress, PHP-FPM, Nginx hiçbiri bu trafiği görmüyor. Cloudflare altındaysan bile, origin IP'yi koruyan bir katman olarak çalışıyor.

WordPress'in Savunmasız Noktaları

wp-login.php her WordPress sitesinde aynı yolda. xmlrpc.php ise varsayılan olarak açık ve sistem.multicall metodu ile tek istekle binlerce brute force denemesi yapılabiliyor. wp-json/wp/v2/users endpoint'i ise kullanıcı adlarını ifşa ediyor. fail2ban bu üç noktayı da ayrı jail'lerle koruyabilir.

doktorbul.com'da 79.000 doktor profili var ve her profil sayfası için ayrı bir URL. Botlar bu sayfalarda gezinirken wp-login.php'yi de tarıyor. İlk ayda günde 12.000 login denemesi görüyorduk. fail2ban kurduktan sonra bu sayı 200'e düştü çünkü ilk 3 denemede IP engelleniyor, botlar listeye girip bir daha gelemiyor.

Jail Dosyası Oluşturma: wordpress-hard.conf

fail2ban'ın konfigürasyonu iki parçadan oluşuyor: jail (hangi koşulda, kaç denemede, ne kadar süre engellenecek) ve filter (log dosyasında hangi pattern aranacak). Jail dosyasını /etc/fail2ban/jail.d/ altında oluşturuyoruz çünkü jail.conf'u doğrudan düzenlemek güncelleme sırasında eziliyor.

Ben wordpress-hard.conf adında bir dosya yaratıyorum:

[wordpress-hard] enabled = true port = http,https filter = wordpress-hard logpath = /var/log/nginx/access.log maxretry = 3 findtime = 600 bantime = 3600 action = iptables-multiport[name=wordpress, port="http,https", protocol=tcp]

Bu konfigürasyon şunu söylüyor: 10 dakika içinde (findtime=600) 3 başarısız deneme (maxretry=3) yapan IP'yi 1 saat (bantime=3600) engelle. logpath kısmı önemli: Nginx kullanıyorsan /var/log/nginx/access.log, Apache'deysen /var/log/apache2/access.log yazacaksın.

futia.net'te findtime=300 ve bantime=86400 kullanıyorum. Yani 5 dakikada 3 deneme yapan IP'yi 24 saat banlıyorum. Çünkü meşru bir kullanıcı 5 dakikada 3 kez yanlış şifre girmez, bot trafiği kesin.

Parametrelerin Detayları

maxretry: Kaç başarısız denemeye izin verileceği. 3 agresif, 5 dengeli, 10 gevşek sayılır. Ben production'da 3 kullanıyorum çünkü wp-login.php'ye yapılan her istek zaten şüpheli.

findtime: Saniye cinsinden zaman penceresi. 600 (10 dakika) standart, ama yoğun siteler için 300 (5 dakika) daha iyi. Çünkü botlar genelde ilk 2-3 dakikada yoğun istek gönderiyor.

bantime: Engelleme süresi. 3600 (1 saat) başlangıç için iyi, ama tekrar eden IP'ler için -1 yazarak kalıcı ban yapabilirsin. Dikkat: kalıcı ban kullanırsan, kendi IP'ni yanlışlıkla banlarsan sunucuya erişemezsin.

action: iptables-multiport hem 80 hem 443 portunu engelliyor. iptables-allports yazarsan tüm portları kapatır, SSH dahil. Sakın bunu yapma.

Filter Dosyası Yazma: wordpress-hard.conf

Filter, log satırlarında aranacak regex pattern'i tanımlıyor. /etc/fail2ban/filter.d/wordpress-hard.conf dosyasını oluştur:

[Definition] failregex = ^ . "POST /wp-login.php ^ . "POST /xmlrpc.php ^ . "GET /wp-login.php." 200 ignoreregex =

Bu filter üç pattern arıyor: 1. POST /wp-login.php: Login denemesi 2. POST /xmlrpc.php: xmlrpc brute force 3. GET /wp-login.php ile 200 response: Başarılı login sayfası yükleme (bot taraması)

kısmı fail2ban'ın IP adresini yakaladığı placeholder. Nginx access.log formatı şöyle:

192.168.1.100 - - [15/Jan/2025:14:32:10 +0000] "POST /wp-login.php HTTP/1.1" 200 1234 "-" "Mozilla/5.0"

Regex bu satırdan 192.168.1.100'ü çıkarıyor. Apache logları biraz farklı olabilir, test etmen gerekiyor.

Gelişmiş Filter: 4xx ve 5xx Response Kodları

Bazı botlar wp-login.php'ye GET isteği gönderiyor ama 404 alıyor (sayfa yok). Bu da sunucu yükü yaratıyor. Gelişmiş filter:

failregex = ^ . "POST /wp-login.php." (200|401|403) ^ . "POST /xmlrpc.php." (200|403|405) ^ . "GET /wp-login.php." 404 ^ . "POST ." 500

Burada 500 hatası veren IP'leri de banlıyoruz çünkü genelde SQL injection veya exploit denemesi yapıyorlar.

diolivo.com.tr'de CartBounty sepet kurtarma otomasyonu çalışırken, bazı botlar /wp-json/wp/v2/users endpoint'ine istek atıp kullanıcı adlarını çekmeye çalışıyordu. Filter'a şunu ekledim:

^ .* "GET /wp-json/wp/v2/users

6 ay sonra bu endpoint'e gelen trafik %95 düştü.

fail2ban Kurulumu ve Servisi Başlatma

Ubuntu/Debian:

sudo apt update sudo apt install fail2ban -y

CentOS/RHEL:

sudo yum install epel-release -y sudo yum install fail2ban -y

Kurulumdan sonra servis otomatik başlamıyor, manuel başlat:

sudo systemctl start fail2ban sudo systemctl enable fail2ban

Durum kontrolü:

sudo systemctl status fail2ban

Aktif jail'leri görmek için:

sudo fail2ban-client status

Çıktı:

Status |- Number of jail: 1 `- Jail list: wordpress-hard

Belirli bir jail'in detayı:

sudo fail2ban-client status wordpress-hard

Burada Currently banned: 5 gibi bir satır görürsen, 5 IP engellenmiş demektir.

Konfigürasyon Değişikliklerini Uygulama

Jail veya filter dosyasını düzenledikten sonra fail2ban'ı yeniden başlatman gerekiyor:

sudo systemctl restart fail2ban

Ama dikkat: restart yaparsan mevcut ban listesi sıfırlanabilir. Sadece konfigürasyonu yeniden yüklemek için:

sudo fail2ban-client reload

Belirli bir jail'i yeniden yükle:

sudo fail2ban-client reload wordpress-hard

Ben futia.net'te her değişiklikten sonra reload kullanıyorum çünkü restart sırasında 2-3 saniyelik bir pencere açılıyor ve botlar o anda girebiliyor.

Filter Test Etme: fail2ban-regex

Filter'ın doğru çalışıp çalışmadığını test etmek için fail2ban-regex komutu var. Gerçek log dosyanızla test edin:

sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-hard.conf

Çıktı:

Running tests =============

Use failregex filter file : wordpress-hard, basedir: /etc/fail2ban Use datepattern : Default Detectors Use log file : /var/log/nginx/access.log Use encoding : UTF-8

Results =======

Failregex: 47 total |- #) [# of hits] regular expression | 1) [32] ^ . "POST /wp-login.php | 2) [12] ^ . "POST /xmlrpc.php | 3) [3] ^ . "GET /wp-login.php." 200 `-

Ignoreregex: 0 total

Date template hits: |- [# of hits] date format | [47] Day(?P<_sep>[-/])MON(?P=_sep)ExYear[ :]?24hour:Minute:Second(?:\.Microseconds)?(?: Zone offset)? `-

Lines: 47 lines, 0 ignored, 47 matched, 0 missed

47 matched demek, log dosyasında 47 satır filter'a uymuş. Eğer 0 matched görürsen, regex'in yanlış veya log formatı farklı demektir.

Manuel Test: Tek Satır ile Deneme

Log dosyasından bir satır kopyala:

192.168.1.100 - - [15/Jan/2025:14:32:10 +0000] "POST /wp-login.php HTTP/1.1" 200 1234 "-" "Mozilla/5.0"

Bu satırı bir dosyaya kaydet (test.log) ve fail2ban-regex ile test et:

echo '192.168.1.100 - - [15/Jan/2025:14:32:10 +0000] "POST /wp-login.php HTTP/1.1" 200 1234 "-" "Mozilla/5.0"' > test.log sudo fail2ban-regex test.log /etc/fail2ban/filter.d/wordpress-hard.conf --print-all-matched

--print-all-matched parametresi hangi satırların eşleştiğini gösteriyor.

memuratamalari.com'da ilan.gov.tr API'sinden günde 50+ ilan çekiyoruz ve her ilan için ayrı bir sayfa oluşturuluyor. Botlar bu sayfaları tararken wp-login.php'yi de deniyordu. fail2ban-regex ile test ettiğimde, GET /wp-login.php pattern'i hiç eşleşmiyordu çünkü Nginx access.log'da "GET /wp-login.php HTTP/1.1" şeklinde yazıyordu ama regex'te boşluk eksikti. Düzelttikten sonra eşleşme başladı.

Gerçek Dünya Senaryosu: italyanmutfagi.com

italyanmutfagi.com'da 618 tarif var ve her tarif Schema.org Recipe ile işaretli. Site ayda 120.000 ziyaretçi alıyor, bunun %15'i bot trafiği. İlk 2 ayda wp-login.php'ye günde 8000 istek geliyordu. fail2ban kurduktan sonra:

1. İlk hafta 340 IP banlandı 2. İkinci hafta 89 IP (tekrar edenler) 3. Üçüncü hafta 12 IP 4. Dördüncü haftadan sonra günde 1-2 IP

CPU kullanımı %45'ten %22'ye düştü. PHP-FPM process sayısı 80'den 35'e indi. Sayfa yükleme süresi 1.2 saniyeden 0.8 saniyeye düştü. Bunların hepsi fail2ban sayesinde, çünkü bot trafiği Nginx seviyesinde kesildi, PHP'ye hiç ulaşmadı.

Cloudflare + fail2ban Kombinasyonu

Cloudflare kullanıyorsan, origin IP'yi gizliyor ama fail2ban yine de çalışmalı. Çünkü Cloudflare'den gelen istekler bile wp-login.php'ye ulaşabiliyor. Cloudflare'in kendi firewall'u var ama ücretsiz planda rate limiting yok. fail2ban bu boşluğu dolduruyor.

Nginx'te gerçek IP'yi almak için:

set_real_ip_from 103.21.244.0/22; set_real_ip_from 103.22.200.0/22; (Cloudflare IP range'lerini ekle) real_ip_header CF-Connecting-IP;

Bu sayede fail2ban log'da Cloudflare IP'sini değil, gerçek ziyaretçi IP'sini görüyor.

Yaygın Hatalar ve Çözümleri

Hata 1: fail2ban servisi başlamıyor

sudo journalctl -u fail2ban -n 50

çıktısına bak. Genelde jail veya filter dosyasında syntax hatası var. Özellikle regex'te kaçış karakterleri (\) eksik olabilir.

Hata 2: Hiçbir IP banlanmıyor

fail2ban-regex ile test et. Eğer 0 matched görüyorsan:

  • Log path yanlış (logpath=/var/log/nginx/access.log kontrol et)
  • Log formatı farklı (Apache vs Nginx)
  • regex pattern hatalı

Hata 3: Kendi IP'ni banladın

fail2ban-client set wordpress-hard unbanip 192.168.1.100

Kalıcı olarak kendi IP'ni whitelist'e ekle. /etc/fail2ban/jail.local dosyası oluştur:

[DEFAULT] ignoreip = 127.0.0.1/8 ::1 192.168.1.100

Hata 4: Ban süresi dolmadan IP tekrar geliyor

sudo iptables -L -n | grep 192.168.1.100

komutu ile IP'nin iptables'da olup olmadığını kontrol et. Eğer yoksa, fail2ban action kısmı çalışmıyor demektir.

Port ve Protocol Sorunları

Bazı sunucularda iptables yerine firewalld kullanılıyor. fail2ban bunu otomatik algılamalı ama bazen action değiştirmen gerekiyor:

action = firewallcmd-multiport[name=wordpress, port="http,https", protocol=tcp]

futia.net'te ilk kurulumda bu hatayı yaşadım. fail2ban servisi çalışıyor, log'larda match görüyordum ama IP'ler banlanmıyordu. firewalld kullandığımı fark edince action'ı değiştirdim, sorun çözüldü.

Ban Listesini İzleme ve Raporlama

Banlı IP'leri görmek:

sudo fail2ban-client status wordpress-hard

Çıktıda Currently banned: 12 ve IP listesi var. Detaylı log:

sudo tail -f /var/log/fail2ban.log

Gerçek zamanlı izleme için. Ban ve unban işlemlerini görüyorsun:

2025-01-15 14:35:22,123 fail2ban.actions [12345]: NOTICE [wordpress-hard] Ban 192.168.1.100 2025-01-15 15:35:22,456 fail2ban.actions [12345]: NOTICE [wordpress-hard] Unban 192.168.1.100

Ben her hafta fail2ban.log'u analiz ediyorum. En çok banlanan IP'leri, hangi saatlerde yoğunluk olduğunu, hangi endpoint'lerin hedef alındığını görüyorum. Bu veri, firewall kurallarını optimize etmek için çok değerli.

Grafana ile Görselleştirme

Gelişmiş kullanım: fail2ban.log'u Promtail ile Loki'ye gönder, Grafana'da görselleştir. Ben futia.net için bunu kurdum, şimdi dashboard'da:

  • Saatlik ban sayısı grafiği
  • En çok banlanan IP'ler (top 10)
  • Jail bazında dağılım (wordpress-hard, ssh, nginx-limit)
  • Coğrafi dağılım (IP'den ülke bilgisi çekiyorum)

Bu setup 2 saat aldı ama şimdi güvenlik durumunu tek bakışta görüyorum.

xmlrpc.php İçin Ayrı Jail

xmlrpc.php özel bir durum çünkü sistem.multicall metodu ile tek istekte binlerce deneme yapılabiliyor. Ayrı bir jail ve filter oluştur:

[xmlrpc-dos] enabled = true port = http,https filter = xmlrpc-dos logpath = /var/log/nginx/access.log maxretry = 1 findtime = 60 bantime = 86400

maxretry=1 dikkat çekici: xmlrpc.php'ye tek istek bile şüpheli, hemen banla. Filter:

[Definition] failregex = ^ .* "POST /xmlrpc.php ignoreregex =

Bu agresif bir yaklaşım ama xmlrpc.php'yi kullanan meşru bir servisin olması çok düşük ihtimal. Jetpack gibi eklentiler kullanıyorsan, xmlrpc gerekli olabilir. O zaman maxretry=5 yap.

doktorbul.com'da xmlrpc.php tamamen kapalı (Nginx seviyesinde 403 dönüyoruz) ama yine de botlar denemeye devam ediyor. fail2ban bu IP'leri de banlıyor, gereksiz 403 response'ları bile ortadan kalkıyor.

Bu kurulumu yaptıktan sonra WordPress siteniz sunucu seviyesinde korunmuş oluyor. Wordfence, Sucuri gibi eklentiler hala önemli (uygulama katmanı koruması için) ama fail2ban kritik bir ilk savunma hattı sağlıyor. Ben 6 yıldır sosyal medya pazarlamasında çalışırken, müşterilerin en büyük derdinin trafik değil güvenlik olduğunu gördüm. Çünkü bir kez hack'lendiğinde, Google sıralamalarını kaybediyorsunuz, müşteri güveni bitiyor.

FUTIA'da site + otomasyon + aylık bakım hizmeti verirken, her projeye fail2ban kurulumu dahil. Çünkü otomasyon çalışırken sunucu performansı kritik, bot trafiği bunu bozuyor. Eğer kendi sunucunuzda WordPress güvenliğini sıfırdan kurmak istiyorsanız veya mevcut fail2ban kurulumunuzu optimize etmek için destek lazımsa, WhatsApp +90 532 491 17 05 veya info@futia.net üzerinden ulaşabilirsiniz. Hollanda'dan çalışıyorum ama Türkiye saatiyle uyumlu yanıt veriyorum.

Sıkça Sorulanlar

fail2ban WordPress'i yavaşlatır mı?

Hayır, tam tersi hızlandırır. fail2ban sistem seviyesinde (iptables) çalışıyor, PHP veya WordPress'e hiç yük bindirmiyor. Engellenen IP'ler sunucuya TCP bağlantısı bile kuramıyor, yani Nginx veya Apache bu trafiği görmüyor. Ben futia.net'te fail2ban kurduktan sonra CPU kullanımı %30 düştü çünkü bot trafiği PHP-FPM'e ulaşmadan kesildi. Tek maliyet, fail2ban'ın log dosyalarını okuması ama bu saniyede birkaç satır olduğu için ihmal edilebilir düzeyde.

Cloudflare kullanıyorsam fail2ban'a ihtiyacım var mı?

Evet, çünkü Cloudflare ücretsiz planda rate limiting sunmuyor ve origin IP'niz biliniyorsa doğrudan hedef alınabilir. Ayrıca Cloudflare'den gelen meşru istekler bile wp-login.php'ye brute force yapabilir. fail2ban, Cloudflare'in firewall'ını tamamlayan bir katman olarak çalışıyor. Nginx'te real_ip_header CF-Connecting-IP ayarını yaparsanız, fail2ban gerçek ziyaretçi IP'sini görebilir ve Cloudflare IP'lerini değil, saldırganı banlar. Ben tüm projelerimde Cloudflare + fail2ban kombinasyonunu kullanıyorum.

fail2ban jail dosyasında maxretry kaç olmalı?

wp-login.php için 3-5 arası ideal. 3 agresif ama güvenli, çünkü meşru bir kullanıcı 10 dakikada 3 kez yanlış şifre girmez. 5 daha dengeli, bazen şifreyi unutan gerçek kullanıcılar için tolerans tanıyor. xmlrpc.php için maxretry=1 yapabilirsiniz çünkü bu endpoint'e yapılan her POST isteği şüpheli. SSH için maxretry=5 öneririm, bazen terminal'de şifre yazarken hata yapılabiliyor. Ben production sunucularımda wp-login için 3, SSH için 5 kullanıyorum. Test ortamında daha yüksek değerler kullanabilirsiniz.

fail2ban filter test ederken 0 matched alıyorum, neden?

Üç ana sebep: 1) Log path yanlış (logpath=/var/log/nginx/access.log doğru mu kontrol edin), 2) Log formatı filter'daki regex'e uymuyor (Nginx vs Apache formatı farklı), 3) Regex pattern'de syntax hatası var. fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-hard.conf --print-all-matched komutu ile detaylı test yapın. Log dosyasından bir satır kopyalayıp regex101.com'da test edebilirsiniz. Ayrıca <HOST> placeholder'ının doğru çalıştığından emin olun, bazı custom log formatlarında IP adresi farklı konumda olabiliyor.

Kendi IP'mi yanlışlıkla banladım, nasıl kaldırırım?

sudo fail2ban-client set wordpress-hard unbanip 192.168.1.100 komutu ile hemen kaldırabilirsiniz (192.168.1.100 yerine kendi IP'nizi yazın). Kalıcı olarak whitelist'e eklemek için /etc/fail2ban/jail.local dosyası oluşturun ve [DEFAULT] bölümüne ignoreip = 127.0.0.1/8 ::1 SIZIN_IP_ADRESINIZ ekleyin. Eğer sunucuya SSH erişiminiz yoksa ve kendi IP'nizi banladıysanız, hosting sağlayıcınızın kontrol panelinden (cPanel, Plesk) veya VPS console'undan giriş yapıp iptables -D INPUT -s 192.168.1.100 -j DROP komutu ile manuel kaldırabilirsiniz.

YAZAR HAKKINDA
Miraç Eroğlu

Hacettepe mezunu, 6 yıldır sosyal medya, 2 yıldır AI otomasyon.

Daha fazla bilgi →

Bu yazıdaki tekniklerden birini uygulamak ister misiniz? Kısa bir form doldurun, 48 saat içinde ücretsiz ön inceleme raporu mailinize düşsün.