n8n ile Sunucu Log Analizi ve Otomatik Uyarı Sistemi Kurulumu

Sunucu loglarını manuel takip etmek, özellikle birden fazla makine yönetiyorsanız, gerçek bir işkence haline gelebilir. Gece 3’te bir sunucuda disk dolduğunda ya da brute force saldırısı başladığında bundan haberdar olmak için sürekli terminale bakar durumda olamazsınız. İşte burada n8n devreye giriyor. Görsel iş akışı mantığıyla karmaşık log analizi pipeline’larını kod yazmadan, ya da çok az kodla, kurabiliyorsunuz. Bu yazıda gerçek dünya senaryolarına dayanan, production’da çalışan bir log analiz ve otomatik uyarı sistemi nasıl kurulur bunu adım adım anlatacağım.

Neden n8n, Neden Log Otomasyonu?

Piyasada ELK Stack, Graylog gibi güçlü log yönetim araçları var. Ama bu araçların ciddi kaynak gereksinimleri var ve küçük/orta ölçekli altyapılar için bazen overkill olabiliyor. n8n ise hem hafif hem de esnek. Bir workflow içinde log okuma, parse etme, filtreleme, veritabanına yazma ve Slack/Telegram’a mesaj atma işlemlerini tek bir akışta birleştirebiliyorsunuz.

n8n’in en büyük avantajı HTTP webhook desteği, zamanlama (cron) sistemi ve 300’den fazla entegrasyon. Bunları bir araya getirince oldukça kapsamlı bir monitoring altyapısı kurmak mümkün.

Kurulum: n8n’i Docker ile Ayağa Kaldırmak

Üretim ortamında n8n’i Docker Compose ile çalıştırmanızı öneririm. Aşağıdaki yapılandırma SQLite yerine PostgreSQL kullanıyor, bu da yüksek hacimli workflow geçmişlerinde çok daha stabil çalışıyor.

mkdir -p /opt/n8n && cd /opt/n8n
# docker-compose.yml
version: "3.8"
services:
  n8n:
    image: n8nio/n8n:latest
    restart: always
    ports:
      - "5678:5678"
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=admin
      - N8N_BASIC_AUTH_PASSWORD=guclu_sifre_buraya
      - N8N_HOST=n8n.sirketiniz.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.sirketiniz.com/
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n_user
      - DB_POSTGRESDB_PASSWORD=db_sifre_buraya
      - GENERIC_TIMEZONE=Europe/Istanbul
    volumes:
      - n8n_data:/home/node/.n8n
      - /var/log:/host/logs:ro
    depends_on:
      - postgres

  postgres:
    image: postgres:15
    restart: always
    environment:
      - POSTGRES_DB=n8n
      - POSTGRES_USER=n8n_user
      - POSTGRES_PASSWORD=db_sifre_buraya
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  n8n_data:
  postgres_data:

Burada kritik bir detay var: /var/log:/host/logs:ro volume mount’u. n8n container’ı host sistemin log dosyalarını salt okunur olarak görebiliyor. Bu güvenlik açısından önemli, container log dosyalarını sadece okuyabiliyor, değiştiremiyor.

docker-compose up -d
docker-compose logs -f n8n

Log Toplama: Log Shipper Script Hazırlama

n8n workflow’larına log verisi göndermek için önce logları bir formata sokmamız gerekiyor. Sunucularınıza yerleştireceğiniz bu script, belirli aralıklarla log dosyalarını okuyup n8n webhook’una gönderecek.

#!/bin/bash
# /usr/local/bin/log_shipper.sh

N8N_WEBHOOK="https://n8n.sirketiniz.com/webhook/log-collector"
HOSTNAME=$(hostname)
LOG_SOURCES=(
    "/var/log/auth.log"
    "/var/log/syslog"
    "/var/log/nginx/error.log"
    "/var/log/nginx/access.log"
)

STATE_FILE="/var/lib/log_shipper/state"
mkdir -p /var/lib/log_shipper

for LOG_FILE in "${LOG_SOURCES[@]}"; do
    if [ ! -f "$LOG_FILE" ]; then
        continue
    fi

    STATE_KEY=$(echo "$LOG_FILE" | md5sum | cut -d' ' -f1)
    LAST_LINE=$(grep "^${STATE_KEY}=" "$STATE_FILE" 2>/dev/null | cut -d'=' -f2)
    LAST_LINE=${LAST_LINE:-0}

    CURRENT_LINES=$(wc -l < "$LOG_FILE")
    
    if [ "$CURRENT_LINES" -le "$LAST_LINE" ]; then
        continue
    fi

    NEW_LOGS=$(tail -n +$((LAST_LINE + 1)) "$LOG_FILE" | head -n 100)
    
    if [ -z "$NEW_LOGS" ]; then
        continue
    fi

    PAYLOAD=$(jq -n 
        --arg hostname "$HOSTNAME" 
        --arg logfile "$LOG_FILE" 
        --arg content "$NEW_LOGS" 
        --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" 
        '{hostname: $hostname, logfile: $logfile, content: $content, timestamp: $timestamp}')

    HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" 
        -X POST 
        -H "Content-Type: application/json" 
        -H "X-API-Key: gizli_api_key_buraya" 
        -d "$PAYLOAD" 
        "$N8N_WEBHOOK")

    if [ "$HTTP_STATUS" == "200" ]; then
        sed -i "/^${STATE_KEY}=/d" "$STATE_FILE"
        echo "${STATE_KEY}=${CURRENT_LINES}" >> "$STATE_FILE"
    fi
done

Bu script’i crontab’a ekleyerek her 5 dakikada bir çalışmasını sağlayın:

chmod +x /usr/local/bin/log_shipper.sh
echo "*/5 * * * * root /usr/local/bin/log_shipper.sh >> /var/log/log_shipper.log 2>&1" >> /etc/cron.d/log-shipper

n8n Workflow 1: SSH Brute Force Tespiti

Bu ilk ve en kritik workflow. /var/log/auth.log dosyasındaki başarısız SSH giriş denemelerini analiz edip belirli bir eşiği aştığında hem Telegram’a uyarı gönderiyor hem de fail2ban’a otomatik ban komutu uyarıyor.

n8n arayüzünde yeni bir workflow oluşturun ve şu node’ları sırayla ekleyin:

Webhook Node (Trigger)

  • Authentication: Header Auth
  • Header Name: X-API-Key
  • Credential Value: gizli_api_key_buraya

Code Node (Log Parser)

// n8n Code Node - SSH Brute Force Parser
const items = $input.all();
const results = [];

for (const item of items) {
  const logContent = item.json.content;
  const hostname = item.json.hostname;
  const lines = logContent.split('n');
  
  const failedAttempts = {};
  
  for (const line of lines) {
    // Failed password pattern
    const failMatch = line.match(/Failed password for (?:invalid user )?(w+) from ([d.]+)/);
    if (failMatch) {
      const user = failMatch[1];
      const ip = failMatch[2];
      const key = `${ip}`;
      
      if (!failedAttempts[key]) {
        failedAttempts[key] = { ip, users: [], count: 0, hostname };
      }
      failedAttempts[key].count++;
      if (!failedAttempts[key].users.includes(user)) {
        failedAttempts[key].users.push(user);
      }
    }
  }
  
  // Sadece 10'dan fazla deneme yapan IP'leri al
  for (const [ip, data] of Object.entries(failedAttempts)) {
    if (data.count >= 10) {
      results.push({
        json: {
          alert_type: 'brute_force',
          ip: data.ip,
          attempt_count: data.count,
          targeted_users: data.users.join(', '),
          hostname: data.hostname,
          severity: data.count >= 50 ? 'critical' : 'warning',
          timestamp: new Date().toISOString()
        }
      });
    }
  }
}

return results.length > 0 ? results : [{ json: { skip: true } }];

IF Node (Filtre)

  • Condition: {{ $json.skip }} is not equal to true

HTTP Request Node (fail2ban API)

Eğer bir fail2ban REST API kurduysanız ya da basit bir webhook ile ban işlemi tetikliyorsanız bu node’u kullanabilirsiniz. Yoksa bir sonraki adıma geçin.

Telegram Node (Uyarı)

  • Chat ID: Telegram grup ya da kanal ID’niz
  • Message:
🚨 *BRUTE FORCE TESPİT EDİLDİ*

🖥️ Sunucu: {{ $json.hostname }}
🌐 Saldırgan IP: {{ $json.ip }}
🔢 Deneme Sayısı: {{ $json.attempt_count }}
👤 Hedef Kullanıcılar: {{ $json.targeted_users }}
⚠️ Önem: {{ $json.severity }}
🕐 Zaman: {{ $json.timestamp }}

Lütfen ilgili IP'yi kontrol edin!

n8n Workflow 2: Disk Kullanım Alarmı

Bu workflow biraz farklı çalışıyor. Doğrudan log analizi değil, cron tabanlı bir kontrol mekanizması. Her 15 dakikada bir tüm sunucularda disk kullanımını kontrol edip kritik eşiği geçenlerde alarm üretiyor.

Schedule Trigger Node

  • Mode: Every X Minutes
  • Minutes: 15

SSH Node (Disk Kontrol)

n8n’in SSH node’u ile direkt sunuculara bağlanıp komut çalıştırabilirsiniz:

df -h | awk 'NR>1 {gsub(/%/,"",$5); if($5+0 >= 80) print $6"|"$5"|"$2"|"$4}'

Code Node (Disk Parser)

const output = $input.first().json.stdout;
const hostname = $input.first().json.hostname || 'bilinmiyor';

if (!output || output.trim() === '') {
  return [{ json: { skip: true } }];
}

const alerts = [];
const lines = output.trim().split('n');

for (const line of lines) {
  if (!line) continue;
  const parts = line.split('|');
  if (parts.length !== 4) continue;
  
  const [mountpoint, usage, total, available] = parts;
  const usageNum = parseInt(usage);
  
  alerts.push({
    json: {
      alert_type: 'disk_usage',
      hostname: hostname,
      mountpoint: mountpoint.trim(),
      usage_percent: usageNum,
      total_size: total.trim(),
      available: available.trim(),
      severity: usageNum >= 95 ? 'critical' : usageNum >= 90 ? 'high' : 'warning',
      message: `${mountpoint.trim()} bölümü %${usageNum} dolu (Toplam: ${total.trim()}, Boş: ${available.trim()})`
    }
  });
}

return alerts.length > 0 ? alerts : [{ json: { skip: true } }];

n8n Workflow 3: Nginx 5xx Hata Spike Tespiti

Web sunucularında kısa sürede çok sayıda 500 hatası oluşması ciddi bir sorunun habercisi. Bu workflow Nginx access loglarını analiz ederek 5 dakikalık pencerede belirli sayının üzerinde 5xx hatası görürse alarm üretiyor.

Code Node (Nginx 5xx Analizi)

const logContent = $input.first().json.content;
const hostname = $input.first().json.hostname;
const lines = logContent.split('n');

const fiveMinAgo = new Date(Date.now() - 5 * 60 * 1000);
const errors5xx = [];
const errorPaths = {};

for (const line of lines) {
  // Nginx combined log format parse
  const match = line.match(/^([d.]+) .+ [(.+)] "(w+) ([^s]+)[^"]*" (d{3}) (d+)/);
  if (!match) continue;
  
  const [, ip, timeStr, method, path, statusCode] = match;
  const status = parseInt(statusCode);
  
  if (status >= 500 && status < 600) {
    const logTime = new Date(timeStr.replace(':', ' ').replace(//(w+)//, '/$1 '));
    
    errors5xx.push({ ip, path, status, time: timeStr });
    errorPaths[path] = (errorPaths[path] || 0) + 1;
  }
}

if (errors5xx.length < 10) {
  return [{ json: { skip: true } }];
}

// En çok hata veren path'leri bul
const topPaths = Object.entries(errorPaths)
  .sort(([,a], [,b]) => b - a)
  .slice(0, 5)
  .map(([path, count]) => `${path}: ${count} hata`)
  .join('n');

return [{
  json: {
    alert_type: 'nginx_5xx_spike',
    hostname: hostname,
    error_count: errors5xx.length,
    top_error_paths: topPaths,
    severity: errors5xx.length >= 50 ? 'critical' : 'high',
    timestamp: new Date().toISOString()
  }
}];

Alarm Deduplication: Aynı Uyarıyı Defalarca Almamak

Üretim ortamında en büyük sorunlardan biri alarm fatigue. Aynı sorun için her 5 dakikada uyarı almak kısa sürede görmezden gelinmeye başlıyor. n8n workflow’larına basit bir deduplication mekanizması ekleyebilirsiniz.

Code Node (Deduplication Kontrolü)

// Bu node bir önceki alarmları statik data'da tutar
// Production'da bunu Redis ya da PostgreSQL ile yapın

const alertKey = `${$json.alert_type}_${$json.hostname}_${$json.ip || $json.mountpoint || 'general'}`;
const now = Date.now();
const cooldownMs = 30 * 60 * 1000; // 30 dakika cooldown

// Static workflow data'dan geçmişi al
const alertHistory = $getWorkflowStaticData('global');

if (!alertHistory.lastAlerts) {
  alertHistory.lastAlerts = {};
}

const lastAlertTime = alertHistory.lastAlerts[alertKey];

if (lastAlertTime && (now - lastAlertTime) < cooldownMs) {
  // Bu alarm için cooldown süresi dolmamış, atla
  return [{ json: { skip: true, reason: 'cooldown aktif', alert_key: alertKey } }];
}

// Alarm yeni ya da cooldown dolmuş, gönder
alertHistory.lastAlerts[alertKey] = now;

// Eski kayıtları temizle (1 saatten eskiler)
for (const [key, time] of Object.entries(alertHistory.lastAlerts)) {
  if (now - time > 3600000) {
    delete alertHistory.lastAlerts[key];
  }
}

return [$input.first()];

Slack Entegrasyonu: Ekip Kanalına Otomatik Mesaj

Telegram dışında Slack kullanan ekipler için entegrasyon çok basit. n8n’de Slack node’unu ekledikten sonra bir Slack App oluşturup bot token almanız yeterli.

Slack uyarı mesajı için Block Kit formatı kullanmak görselliği artırıyor:

// HTTP Request node body (Slack webhook)
const severity = $json.severity;
const emoji = severity === 'critical' ? '🔴' : severity === 'high' ? '🟠' : '🟡';

return {
  blocks: [
    {
      type: "header",
      text: {
        type: "plain_text",
        text: `${emoji} Sunucu Uyarısı: ${$json.alert_type.toUpperCase()}`
      }
    },
    {
      type: "section",
      fields: [
        { type: "mrkdwn", text: `*Sunucu:*n${$json.hostname}` },
        { type: "mrkdwn", text: `*Önem Seviyesi:*n${$json.severity}` },
        { type: "mrkdwn", text: `*Zaman:*n${$json.timestamp}` },
        { type: "mrkdwn", text: `*Detay:*n${$json.message || JSON.stringify($json)}` }
      ]
    }
  ]
};

PagerDuty veya OpsGenie Entegrasyonu

Kritik alarmlar için on-call rotasyonu yönetmek gerekiyorsa n8n’i OpsGenie ile entegre edebilirsiniz. Sadece bir HTTP Request node’u yeterli:

  • Method: POST
  • URL: https://api.opsgenie.com/v2/alerts
  • Headers: Authorization: GenieKey API_KEY_BURAYA
  • Body:
{
  "message": "{{ $json.alert_type }} - {{ $json.hostname }}",
  "description": "{{ $json.message }}",
  "priority": "{{ $json.severity === 'critical' ? 'P1' : 'P2' }}",
  "tags": ["n8n", "automated", "{{ $json.alert_type }}"]
}

Workflow Monitoring: n8n’in Kendisini İzlemek

n8n çalıştığı sürece her şey güzel ama ya n8n kendisi çöküse? Bunun için basit bir health check mekanizması kurmak şart.

#!/bin/bash
# /usr/local/bin/n8n_healthcheck.sh

N8N_URL="https://n8n.sirketiniz.com/healthz"
TELEGRAM_TOKEN="BOT_TOKEN"
CHAT_ID="CHAT_ID"
LAST_STATE_FILE="/var/lib/n8n_monitor/last_state"

mkdir -p /var/lib/n8n_monitor

HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$N8N_URL")
LAST_STATE=$(cat "$LAST_STATE_FILE" 2>/dev/null || echo "unknown")

if [ "$HTTP_CODE" != "200" ]; then
    if [ "$LAST_STATE" != "down" ]; then
        curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" 
            -d chat_id="$CHAT_ID" 
            -d text="🚨 n8n sunucusu yanıt vermiyor! HTTP: $HTTP_CODE - $(date)" 
            -d parse_mode="Markdown"
        echo "down" > "$LAST_STATE_FILE"
    fi
else
    if [ "$LAST_STATE" == "down" ]; then
        curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" 
            -d chat_id="$CHAT_ID" 
            -d text="✅ n8n sunucusu tekrar ayakta! - $(date)" 
            -d parse_mode="Markdown"
    fi
    echo "up" > "$LAST_STATE_FILE"
fi
chmod +x /usr/local/bin/n8n_healthcheck.sh
echo "*/3 * * * * root /usr/local/bin/n8n_healthcheck.sh" >> /etc/cron.d/n8n-monitor

Güvenlik Notları

n8n webhook’larını açık internete açıyorsanız mutlaka şu önlemleri alın:

  • Header Authentication: Her webhook’a unique bir API key ekleyin
  • IP Whitelist: Nginx ya da firewall seviyesinde sadece sunucularınızın IP’lerinden gelen istekleri kabul edin
  • Rate Limiting: Nginx üzerinde webhook endpoint’lerine rate limit koyun
  • HTTPS Zorunluluğu: HTTP üzerinden log verisi göndermeyin, TLS şifreleme şart
  • Minimal Volume Mount: Docker volume mount’larını sadece ihtiyaç duyulan dizinlerle sınırlı tutun ve her zaman read-only (ro) flagini kullanın
  • Ayrı Network: n8n container’ını production servislerle aynı Docker network’e koymayın

Sonuç

n8n ile kurduğunuz bu log analiz sistemi, küçük bir altyapı için ELK Stack’in bütçe ve kaynak yükü olmadan ciddi bir monitoring kapasitesi sunuyor. Brute force saldırıları, disk dolma, uygulama hata spike’ları gibi en kritik senaryoları otomatik olarak yakalayıp size ya da ekibinize iletebiliyorsunuz.

Sistemin gerçek değeri birikimde ortaya çıkıyor. İlk birkaç haftada false positive’leri ayıklayın, eşik değerlerini kendi altyapınıza göre ayarlayın ve deduplication süresini ihtiyacınıza göre düzenleyin. Zamanla hangi alarmların gerçekten önemli olduğunu öğrenecek ve workflow’larınızı buna göre şekillendireceksiniz. n8n’in görsel arayüzü bu iterasyonları çok hızlı yapmanıza olanak tanıyor. Bir workflow’ı değiştirmek için servis yeniden başlatmaya gerek yok, kaydettiğiniz anda değişiklik aktif oluyor.

Bir sonraki adım olarak bu sistemi metrics toplama ile birleştirmeyi ve Grafana dashboard’larına bağlamayı düşünebilirsiniz. Ama bu konu başlı başına ayrı bir yazı.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir