Yoğun trafik alan bir sunucuda fail2ban’ın yavaşladığını fark ettiğinizde, genellikle ilk şüphelendiğiniz yer yanlış olur. Çoğu sysadmin doğrudan jail konfigürasyonuna bakar, oysa asıl sorun büyük log dosyalarının işlenmesinde gizlidir. Günde milyonlarca satır üreten bir nginx veya SSH log dosyasıyla çalışırken fail2ban’ın CPU kullanımının %80’e çıktığını gördüyseniz, bu yazı tam size göre.
Sorun Neden Ortaya Çıkar?
fail2ban özünde bir log takip aracıdır. Her jail için tanımladığınız filtreler, düzenli ifadeler (regex) kullanarak log dosyalarını tarar. Küçük bir VPS’te bu süreç saniyeler içinde tamamlanır, fakat yüksek trafikli bir sunucuda durum farklıdır.
Tipik problemler şunlardır:
- Log dosyası büyümesi:
/var/log/auth.logdosyası günde 500MB’a ulaşabilir - Regex karmaşıklığı: Kötü yazılmış regex ifadeleri her satır için aşırı CPU harcar
- Polling gecikmesi: fail2ban’ın dosyayı ne sıklıkla kontrol ettiği kritik öneme sahiptir
- Veritabanı yükü: SQLite veritabanına yapılan sürekli yazma işlemleri I/O darboğazı yaratır
- Aşırı jail sayısı: 20-30 aktif jail aynı anda çalışıyorsa bellek kullanımı ciddi biçimde artar
Bir prodüksiyon örneği verelim: E-ticaret sitesi barındıran bir sunucuda SSH brute force saldırıları sırasında auth.log dakikada 10.000 satır üretiyordu. fail2ban bu yükü kaldıramayıp 45 saniye gecikmeyle ban işlemi yapıyordu. O 45 saniyede saldırgan yüzlerce deneme daha yapabiliyordu.
Önce Mevcut Durumu Ölçün
Optimize etmeden önce neyi optimize ettiğinizi bilmeniz gerekir. fail2ban’ın performansını izlemek için birkaç pratik yöntem var.
# fail2ban'ın CPU ve bellek kullanımını izle
top -p $(pgrep fail2ban)
# Daha detaylı kaynak kullanımı için
pidstat -p $(pgrep fail2ban) 1 10
# fail2ban log dosyasındaki hata ve uyarıları say
grep -c "WARNING|ERROR" /var/log/fail2ban.log
# Son 100 satırdaki ban işlem sürelerini incele
tail -100 /var/log/fail2ban.log | grep "Ban|Found"
fail2ban-client ile de hızlı durum kontrolü yapabilirsiniz:
# Tüm jail'lerin durumunu listele
fail2ban-client status
# Spesifik bir jail için detay
fail2ban-client status sshd
# fail2ban'ın ping süresini ölç (gecikme testi)
time fail2ban-client ping
Eğer time fail2ban-client ping komutu 1 saniyeden uzun sürüyorsa, ciddi bir performans sorununuz var demektir. Normal şartlarda bu işlem 50ms altında tamamlanmalıdır.
Log Rotasyonu ve fail2ban Entegrasyonu
En temel ama en çok ihmal edilen konudan başlayalım: log rotasyonu. Büyük log dosyaları fail2ban’ı direkt etkiler çünkü her yeniden başlatmada tüm dosya yeniden taranır.
# /etc/logrotate.d/custom-fail2ban-aware dosyasını oluşturun
cat > /etc/logrotate.d/nginx-fail2ban << 'EOF'
/var/log/nginx/access.log
/var/log/nginx/error.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
sharedscripts
postrotate
# Nginx'e yeni log dosyası açmasını söyle
[ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
# fail2ban'a log rotasyonu olduğunu bildir
fail2ban-client set nginx-req-limit unbanip 0.0.0.0 2>/dev/null || true
endscript
}
EOF
fail2ban kendi ayarlarında log rotasyonunu nasıl ele aldığını jail.local içinde belirtebilirsiniz:
# /etc/fail2ban/jail.local
[DEFAULT]
# Log rotasyonundan sonra fail2ban'ın davranışı
# false = rotasyondan sonra dosyayı yeniden okumaya başla
# true = sadece yeni satırları oku (büyük dosyalar için önerilir)
logtarget = /var/log/fail2ban.log
# findtime değerini makul tutun - çok büyük yaparsanız daha fazla log taranır
findtime = 600
# Veritabanı temizleme süresi
dbpurgeage = 86400
Performans Kritik Ayarlar: jail.local Optimizasyonu
# /etc/fail2ban/jail.local - Performans odaklı konfigürasyon
[DEFAULT]
# Backend seçimi performans için kritiktir
# systemd > pyinotify > polling sıralamasını takip edin
backend = systemd
# Polling backend kullanıyorsanız kontrol aralığını artırın
# Varsayılan 1 saniyedir, yüksek yük altında 5-10 saniye yapın
pollinterval = 5
# findtime: Ne kadar geriye bakılacak (saniye)
# Büyük loglar için bu değeri küçük tutun
findtime = 300
# maxretry: Kaç denemeden sonra ban
maxretry = 5
# bantime: Ban süresi - uzun tutmak tekrar tarama yükünü azaltır
bantime = 3600
# Incremental ban: Her tekrar banlamada süre katlansın
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 604800
# Veritabanı yolu - SSD üzerinde olmasına dikkat edin
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
# Veritabanı güncellemelerini belleğe al, diske toplu yaz
# Bu I/O yükünü dramatik biçimde azaltır
dbpurgeage = 86400
backend seçimi burada en kritik parametredir:
- systemd: journald ile entegre çalışır, en verimli yöntemdir
- pyinotify: Kernel’ın inotify mekanizmasını kullanır, polling’den iyidir
- polling: Dosyayı periyodik olarak kontrol eder, en yavaş yöntemdir
- auto: Sisteme göre otomatik seçim yapar, genellikle iyi çalışır
Regex Optimizasyonu: En Büyük Hız Kazanımı
Kötü yazılmış regex ifadeleri fail2ban performansını on kat yavaşlatabilir. Her log satırı için çalışan bir regex, milyonlarca satırda ciddi gecikme yaratır.
Önce mevcut filterlerinizin ne kadar sürdüğünü test edin:
# Bir filtrenin belirli bir log dosyasındaki performansını test et
time fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
# Daha detaylı analiz için
fail2ban-regex --print-all-matched /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf 2>&1 | head -50
# Sadece istatistikleri göster
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf | tail -20
Şimdi kötü ve iyi regex örneklerini karşılaştıralım:
# /etc/fail2ban/filter.d/nginx-custom.conf - Kötü regex örneği
[Definition]
# Bu regex gereksiz yere karmaşık ve yavaş
# .* kullanımı backtracking'e neden olur
failregex = ^.*- -.*"(GET|POST|PUT|DELETE|HEAD|OPTIONS|PATCH) .* HTTP/.*" (4[0-9]{2}|5[0-9]{2}) .*$
# İyi regex örneği - daha spesifik, daha hızlı
[Definition]
# IP adresini başa al, gereksiz grup yakalamayı kaldır
# (?:...) kullanarak yakalamayan grup tanımla
failregex = ^<HOST> - S+ [S+ +d{4}] "(?:GET|POST) S+ HTTP/d.d" (?:400|401|403|404|429) d+
Regex yaratırken dikkat edilmesi gerekenler:
.*kullanımını minimumda tutun: Greedy matching gereksiz CPU harcaretiketini doğru konuma koyun: IP adresi genellikle satırın başındadır(?:...)kullanın: Yakalamayan gruplar(...)gruplarından daha hızlıdır- Alternation’ı sınırlayın:
(a|b|c|d|e)yerine karakter sınıfı[abcde]kullanın
Büyük Log Dosyaları için systemd Journal Kullanımı
Eğer servisleriniz systemd altında çalışıyorsa, dosya tabanlı log yerine doğrudan journald’den okuma yapın. Bu yöntem özellikle yüksek yük altında çok daha verimlidir.
# /etc/fail2ban/jail.local içinde SSH için systemd backend
[sshd]
enabled = true
backend = systemd
journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd
maxretry = 3
findtime = 300
bantime = 3600
# journald'nin fail2ban ile doğru çalıştığını doğrula
journalctl -u sshd --since "1 hour ago" | grep "Failed password" | wc -l
# fail2ban'ın journal'dan okuduğunu kontrol et
fail2ban-client status sshd | grep "Currently banned"
# systemd backend ile fail2ban'ı yeniden başlat ve log'u izle
systemctl restart fail2ban
journalctl -fu fail2ban
Veritabanı Optimizasyonu
fail2ban SQLite kullandığı için, veritabanı boyutu büyüdükçe yavaşlar. Özellikle bantime.increment kullanıyorsanız veritabanı hızla büyüyebilir.
# Veritabanı boyutunu kontrol et
ls -lh /var/lib/fail2ban/fail2ban.sqlite3
# SQLite içine bağlanıp tablo boyutlarını incele
sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 << 'EOF'
.tables
SELECT name, COUNT(*) as kayit_sayisi FROM bans GROUP BY name;
SELECT COUNT(*) as toplam_ban FROM bans;
SELECT COUNT(*) as toplam_ip FROM bips;
.quit
EOF
# Eski kayıtları temizle (30 günden eski)
sqlite3 /var/lib/fail2ban/fail2ban.sqlite3
"DELETE FROM bans WHERE timeofban < strftime('%s', 'now') - 2592000;"
# Veritabanını optimize et (VACUUM işlemi)
sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 "VACUUM;"
Bu işlemi otomatikleştirmek için bir cron job oluşturun:
# /etc/cron.weekly/fail2ban-db-cleanup
cat > /etc/cron.weekly/fail2ban-db-cleanup << 'EOF'
#!/bin/bash
DB="/var/lib/fail2ban/fail2ban.sqlite3"
if [ -f "$DB" ]; then
# 30 günden eski kayıtları sil
sqlite3 "$DB" "DELETE FROM bans WHERE timeofban < strftime('%s', 'now') - 2592000;" 2>/dev/null
# Veritabanını optimize et
sqlite3 "$DB" "VACUUM;" 2>/dev/null
echo "fail2ban veritabani temizlendi: $(date)" >> /var/log/fail2ban-maintenance.log
fi
EOF
chmod +x /etc/cron.weekly/fail2ban-db-cleanup
Çok Sayıda Jail ile Çalışma Stratejisi
20’den fazla aktif jail çalıştırıyorsanız, öncelik sıralaması yapmanız gerekir. Her jail ayrı bir thread’de çalışır ve bellek tüketir.
# Kaç aktif jail var?
fail2ban-client status | grep "Jail list" | tr ',' 'n' | wc -l
# Her jail'in ban sayısını görüntüle
for jail in $(fail2ban-client status | grep "Jail list" | sed 's/.*Jail list:s*//' | tr ',' ' '); do
banned=$(fail2ban-client status $jail 2>/dev/null | grep "Currently banned" | awk '{print $NF}')
echo "$jail: $banned banned IP"
done
Az kullanılan jail’leri tespit edip kapatın:
# Son 7 günde hiç ban yapmamış jail'leri bul
for jail in $(fail2ban-client status | grep "Jail list" | sed 's/.*Jail list:s*//' | tr ',' ' '); do
total=$(fail2ban-client status $jail 2>/dev/null | grep "Total banned" | awk '{print $NF}')
if [ "$total" = "0" ]; then
echo "Devre disi birakilabilir: $jail"
fi
done
ipset ile Firewall Performansını Artırma
Çok sayıda IP banladığınızda, iptables kuralları uzar ve her paket için tarama süresi artar. ipset kullanmak bu sorunu çözer.
# ipset kurulu mu kontrol et
which ipset || apt-get install ipset -y
# fail2ban'ın ipset kullanmasını sağla
# /etc/fail2ban/action.d/iptables-ipset-proto6.conf dosyasını kullanın
# ipset action'ı varsayılan yap
cat >> /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
# Standart iptables yerine ipset kullan
banaction = iptables-ipset-proto6
banaction_allports = iptables-ipset-proto6-allports
EOF
# Değişikliği uygula
systemctl restart fail2ban
# ipset tablosunun oluşturulduğunu doğrula
ipset list | grep "fail2ban"
# Kaç IP banlandığını hızlıca görüntüle
ipset list fail2ban-sshd | grep "Number of entries"
Gerçek Dünya Senaryosu: WordPress Sitesi Altında Ezilmek
Bir müşterinin 5 WordPress sitesi barındıran sunucusunda yaşanan problemi anlatalım. Sunucu özellikleri: 4 vCPU, 8GB RAM, HDD disk.
Problem: fail2ban sürekli %95 CPU kullanıyordu. access.log günde 2GB büyüyordu.
# İlk teşhis
tail -f /var/log/fail2ban.log | grep "ERROR|WARNING"
# Çıktı: WARNING: Lots of fails detected...
# Log boyutunu kontrol et
du -sh /var/log/nginx/access.log
# Çıktı: 1.8G /var/log/nginx/access.log
# fail2ban-regex ile mevcut filtreyi test et
time fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-http-auth.conf
# Çıktı: real 4m32.156s - 4.5 dakika! Kabul edilemez.
Uygulanan çözümler sıralamasıyla:
# 1. Log rotasyonunu günlük bazdan saatlik bazına al
cat > /etc/logrotate.d/nginx << 'EOF'
/var/log/nginx/*.log {
hourly
rotate 24
compress
delaycompress
missingok
notifempty
sharedscripts
postrotate
nginx -s reopen
endscript
}
EOF
# 2. Logrotate'i saat başı çalıştır
echo "0 * * * * root /usr/sbin/logrotate /etc/logrotate.d/nginx" > /etc/cron.d/nginx-rotate
# 3. fail2ban konfigürasyonunu optimize et
# findtime'ı 1 saatten 5 dakikaya indir
# Bu sayede fail2ban daha az log taramak zorunda kalır
sed -i 's/findtime = 3600/findtime = 300/' /etc/fail2ban/jail.local
# 4. polling yerine pyinotify kullan
sed -i 's/backend = polling/backend = pyinotify/' /etc/fail2ban/jail.local
# Sonucu test et
systemctl restart fail2ban
sleep 30
time fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-http-auth.conf
# Yeni çıktı: real 0m8.432s - 8 saniye. 30 kat iyileşme!
İzleme ve Alarm Sistemi Kurma
Optimize ettiğiniz sistemi izlemek de bir o kadar önemlidir. Basit bir izleme scripti:
#!/bin/bash
# /usr/local/bin/fail2ban-monitor.sh
THRESHOLD_CPU=50
THRESHOLD_BANNED=1000
LOG_FILE="/var/log/fail2ban-monitor.log"
# fail2ban PID'ini bul
F2B_PID=$(pgrep fail2ban-server)
if [ -z "$F2B_PID" ]; then
echo "$(date): KRITIK - fail2ban calismıyor!" | tee -a $LOG_FILE
systemctl start fail2ban
exit 1
fi
# CPU kullanımını kontrol et
CPU_USAGE=$(ps -p $F2B_PID -o %cpu --no-headers | awk '{print int($1)}')
if [ "$CPU_USAGE" -gt "$THRESHOLD_CPU" ]; then
echo "$(date): UYARI - fail2ban CPU kullanimi: %$CPU_USAGE" | tee -a $LOG_FILE
fi
# Toplam ban sayısını kontrol et
TOTAL_BANNED=0
for jail in $(fail2ban-client status 2>/dev/null | grep "Jail list" | sed 's/.*Jail list:s*//' | tr ',' ' '); do
banned=$(fail2ban-client status $jail 2>/dev/null | grep "Currently banned" | awk '{print $NF}')
TOTAL_BANNED=$((TOTAL_BANNED + banned))
done
echo "$(date): Toplam banlanan IP: $TOTAL_BANNED, CPU: %$CPU_USAGE" >> $LOG_FILE
# Cron ile her 5 dakikada bir çalıştır
# */5 * * * * root /usr/local/bin/fail2ban-monitor.sh
chmod +x /usr/local/bin/fail2ban-monitor.sh
# Cron'a ekle
echo "*/5 * * * * root /usr/local/bin/fail2ban-monitor.sh" > /etc/cron.d/fail2ban-monitor
Sonuç
fail2ban performans optimizasyonu tek seferlik bir iş değildir. Sunucunuzun trafiği büyüdükçe, saldırı örüntüleri değiştikçe ayarlarınızı da güncellemek zorundasınız.
Öncelik sırasıyla yapmanız gerekenler şöyle özetlenebilir:
- Önce ölçün:
fail2ban-regexvetopile mevcut durumu belgeleyin - Backend’i optimize edin: Mümkünse systemd, değilse pyinotify kullanın
- Log rotasyonunu düzeltin: Büyük log dosyaları en yaygın performans katilidir
- findtime değerini küçültün: Ne kadar az geriye bakılırsa o kadar az tarama yapılır
- Regex’lerinizi gözden geçirin:
.*kullanımını minimuma indirin - ipset’e geçin: Çok sayıda ban kuralı için zorunludur
- Veritabanını düzenli temizleyin: SQLite şişmeye meyillidir
- İzleme kurun: Sorun büyümeden haberdar olun
Gerçek dünyada hiçbir zaman “mükemmel” bir konfigürasyon yoktur. Her sunucunun trafik profili, saldırı yüzeyi ve donanım kapasitesi farklıdır. Bu yüzden buradaki teknikleri kendi ortamınıza uyarlayın, test edin ve ölçün. Körü körüne kopyala-yapıştır yapmak bazen işe yarar, ama çoğunlukla yeni problemler yaratır.