Configure advanced nftables logging and monitoring for network security

Advanced 45 min May 26, 2026 107 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up comprehensive nftables logging with structured syslog integration, Prometheus metrics collection, and ELK stack analysis. Configure Grafana dashboards for real-time firewall monitoring and automated alerting for security events.

Prerequisites

  • Root access to the server
  • Basic knowledge of nftables and iptables
  • Prometheus and Grafana installed
  • Elasticsearch cluster available

What this solves

nftables provides powerful packet filtering capabilities, but without proper logging and monitoring, security events go unnoticed. This tutorial configures advanced nftables logging with structured output, integrates with Prometheus for metrics collection, and sets up ELK stack analysis with Grafana dashboards for comprehensive firewall monitoring and automated security alerting.

Step-by-step configuration

Install nftables and logging components

Install nftables, rsyslog for structured logging, and monitoring tools for metrics collection.

sudo apt update
sudo apt install -y nftables rsyslog rsyslog-gnutls python3-prometheus-client filebeat
sudo dnf install -y nftables rsyslog rsyslog-gnutls python3-prometheus-client filebeat

Configure nftables with structured logging

Create a comprehensive nftables configuration with detailed logging for different traffic types and security events.

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority filter; policy drop;
        
        # Allow loopback traffic
        iif "lo" accept
        
        # Allow established and related connections
        ct state established,related accept
        
        # Log and allow SSH with rate limiting
        tcp dport 22 limit rate 10/minute burst 5 packets log prefix "NFTABLES-SSH-ALLOW: " level info accept
        tcp dport 22 log prefix "NFTABLES-SSH-DROP: " level warn drop
        
        # Log and allow HTTP/HTTPS
        tcp dport { 80, 443 } log prefix "NFTABLES-WEB-ALLOW: " level info accept
        
        # Log suspicious port scans
        tcp flags & (fin|syn|rst|psh|ack|urg) == syn limit rate 1/second burst 5 packets log prefix "NFTABLES-SCAN-DETECT: " level warn
        
        # Log and drop invalid packets
        ct state invalid log prefix "NFTABLES-INVALID: " level warn drop
        
        # Log ICMP for network troubleshooting
        icmp type { echo-request, destination-unreachable, time-exceeded } limit rate 5/second log prefix "NFTABLES-ICMP: " level info accept
        icmpv6 type { echo-request, destination-unreachable, time-exceeded, nd-neighbor-solicit, nd-neighbor-advert } limit rate 5/second log prefix "NFTABLES-ICMP6: " level info accept
        
        # Log everything else before dropping
        log prefix "NFTABLES-INPUT-DROP: " level notice
    }
    
    chain forward {
        type filter hook forward priority filter; policy drop;
        
        # Log forwarded traffic
        ct state established,related accept
        log prefix "NFTABLES-FORWARD-DROP: " level notice
    }
    
    chain output {
        type filter hook output priority filter; policy accept;
        
        # Log outgoing connections to suspicious ports
        tcp dport { 1433, 3389, 5432, 6379 } log prefix "NFTABLES-OUT-SUSPICIOUS: " level warn
    }
    
    # Custom chain for intrusion detection
    chain intrusion_detection {
        # Log multiple failed SSH attempts from same IP
        tcp dport 22 ct state new @ssh_attempts { ip saddr limit rate over 5/minute } log prefix "NFTABLES-SSH-BRUTE: " level err drop
        
        # Log port scan attempts
        tcp flags & (fin|syn) == (fin|syn) log prefix "NFTABLES-XMAS-SCAN: " level err drop
        tcp flags & (fin|syn|rst|psh|ack|urg) == 0 log prefix "NFTABLES-NULL-SCAN: " level err drop
    }
}

Create named sets for tracking

table inet filter { set ssh_attempts { type ipv4_addr size 1000 timeout 1h flags dynamic } }

Configure rsyslog for structured nftables logging

Set up rsyslog to capture nftables logs with structured formatting and separate log files for different event types.

# nftables logging configuration

Create separate log files for different nftables events

Define templates for structured logging

$template NFTablesFormat,"%timestamp:::date-rfc3339% %hostname% %syslogtag% [%structured-data%] %msg%\n" $template NFTablesJSONFormat,"{\"timestamp\":\"%timestamp:::date-rfc3339%\",\"hostname\":\"%hostname%\",\"facility\":\"%syslogfacility-text%\",\"severity\":\"%syslogseverity-text%\",\"tag\":\"%syslogtag%\",\"message\":\"%msg%\"}\n"

SSH-related logs

:msg, contains, "NFTABLES-SSH" /var/log/nftables/ssh.log;NFTablesFormat :msg, contains, "NFTABLES-SSH" /var/log/nftables/ssh.json;NFTablesJSONFormat

Web traffic logs

:msg, contains, "NFTABLES-WEB" /var/log/nftables/web.log;NFTablesFormat :msg, contains, "NFTABLES-WEB" /var/log/nftables/web.json;NFTablesJSONFormat

Security incident logs

:msg, contains, "NFTABLES-SCAN\|NFTABLES-BRUTE\|NFTABLES-XMAS\|NFTABLES-NULL" /var/log/nftables/security.log;NFTablesFormat :msg, contains, "NFTABLES-SCAN\|NFTABLES-BRUTE\|NFTABLES-XMAS\|NFTABLES-NULL" /var/log/nftables/security.json;NFTablesJSONFormat

General nftables logs

:msg, contains, "NFTABLES" /var/log/nftables/general.log;NFTablesFormat :msg, contains, "NFTABLES" /var/log/nftables/general.json;NFTablesJSONFormat

Stop processing nftables messages after logging

:msg, contains, "NFTABLES" stop

Create log directories and set permissions

Create the nftables log directory structure with proper permissions for security and log rotation.

sudo mkdir -p /var/log/nftables
sudo chown syslog:adm /var/log/nftables
sudo chmod 755 /var/log/nftables
sudo systemctl restart rsyslog

Configure log rotation for nftables logs

Set up logrotate to manage nftables log files with compression and retention policies.

/var/log/nftables/.log /var/log/nftables/.json {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    create 644 syslog adm
    sharedscripts
    postrotate
        /usr/lib/rsyslog/rsyslog-rotate
    endscript
    # Send HUP signal to rsyslog
    postrotate
        systemctl reload rsyslog > /dev/null 2>&1 || true
    endscript
}

Create Prometheus exporter for nftables metrics

Develop a Python script that parses nftables logs and exposes metrics for Prometheus scraping.

#!/usr/bin/env python3
import re
import time
import json
from collections import defaultdict, Counter
from prometheus_client import start_http_server, Counter as PrometheusCounter, Gauge, Histogram
import threading
import subprocess

Prometheus metrics

nftables_packets_total = PrometheusCounter( 'nftables_packets_total', 'Total packets processed by nftables', ['chain', 'action', 'protocol'] ) nftables_bytes_total = PrometheusCounter( 'nftables_bytes_total', 'Total bytes processed by nftables', ['chain', 'action', 'protocol'] ) nftables_connections_active = Gauge( 'nftables_connections_active', 'Active connections tracked by nftables' ) nftables_security_events = PrometheusCounter( 'nftables_security_events_total', 'Security events detected by nftables', ['event_type', 'source_ip'] ) nftables_rule_hits = PrometheusCounter( 'nftables_rule_hits_total', 'Number of times each rule was triggered', ['rule_name'] ) class NFTablesMetricsCollector: def __init__(self): self.ssh_attempts = defaultdict(int) self.blocked_ips = set() def parse_nftables_log(self, log_line): """Parse nftables log entries and extract metrics""" try: # Parse JSON logs for structured data if log_line.startswith('{'): log_data = json.loads(log_line) message = log_data.get('message', '') else: message = log_line # Extract rule information if 'NFTABLES-SSH-ALLOW' in message: nftables_packets_total.labels(chain='input', action='allow', protocol='tcp').inc() nftables_rule_hits.labels(rule_name='ssh_allow').inc() elif 'NFTABLES-SSH-DROP' in message: nftables_packets_total.labels(chain='input', action='drop', protocol='tcp').inc() nftables_rule_hits.labels(rule_name='ssh_drop').inc() elif 'NFTABLES-WEB-ALLOW' in message: nftables_packets_total.labels(chain='input', action='allow', protocol='tcp').inc() nftables_rule_hits.labels(rule_name='web_allow').inc() elif 'NFTABLES-SSH-BRUTE' in message: src_ip = self.extract_ip(message) if src_ip: nftables_security_events.labels(event_type='ssh_brute_force', source_ip=src_ip).inc() self.ssh_attempts[src_ip] += 1 elif 'NFTABLES-SCAN-DETECT' in message: src_ip = self.extract_ip(message) if src_ip: nftables_security_events.labels(event_type='port_scan', source_ip=src_ip).inc() elif 'NFTABLES-INVALID' in message: nftables_security_events.labels(event_type='invalid_packet', source_ip='unknown').inc() except Exception as e: print(f"Error parsing log line: {e}") def extract_ip(self, message): """Extract IP address from log message""" ip_pattern = r'SRC=(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' match = re.search(ip_pattern, message) return match.group(1) if match else None def update_connection_stats(self): """Update active connection statistics""" try: result = subprocess.run(['nft', 'list', 'table', 'inet', 'filter'], capture_output=True, text=True) # Parse nftables output for connection tracking info # This is a simplified example - real implementation would be more complex active_conns = len(re.findall(r'ct state established', result.stdout)) nftables_connections_active.set(active_conns) except Exception as e: print(f"Error updating connection stats: {e}") def monitor_logs(self): """Monitor nftables log files for new entries""" import subprocess # Monitor JSON log file for new entries cmd = ['tail', '-F', '/var/log/nftables/general.json'] process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) for line in iter(process.stdout.readline, ''): if line.strip(): self.parse_nftables_log(line.strip()) def main(): collector = NFTablesMetricsCollector() # Start Prometheus metrics server start_http_server(9090) print("nftables exporter started on port 9090") # Start log monitoring in background thread log_thread = threading.Thread(target=collector.monitor_logs) log_thread.daemon = True log_thread.start() # Periodic connection stats update while True: collector.update_connection_stats() time.sleep(30) if __name__ == '__main__': main()

Create systemd service for nftables exporter

Configure the nftables Prometheus exporter as a systemd service for automatic startup and monitoring.

[Unit]
Description=NFTables Prometheus Exporter
After=network.target nftables.service
Requires=nftables.service

[Service]
Type=simple
User=nftables-exporter
Group=nftables-exporter
ExecStart=/usr/local/bin/nftables_exporter.py
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1

Security settings

NoNewPrivileges=true PrivateTmp=true ProtectSystem=strict ProtectHome=true ReadWritePaths=/var/log/nftables [Install] WantedBy=multi-user.target

Create dedicated user for nftables exporter

Create a dedicated system user for running the nftables exporter with minimal privileges.

sudo useradd --system --no-create-home --shell /bin/false nftables-exporter
sudo usermod -a -G adm nftables-exporter
sudo chmod +x /usr/local/bin/nftables_exporter.py

Configure Filebeat for ELK Stack integration

Set up Filebeat to ship nftables logs to Elasticsearch for analysis and long-term storage.

filebeat.inputs:
  • type: log
enabled: true paths: - /var/log/nftables/security.json fields: log_type: nftables_security fields_under_root: true json.keys_under_root: true json.add_error_key: true multiline.pattern: '^{' multiline.negate: true multiline.match: after
  • type: log
enabled: true paths: - /var/log/nftables/ssh.json fields: log_type: nftables_ssh fields_under_root: true json.keys_under_root: true json.add_error_key: true multiline.pattern: '^{' multiline.negate: true multiline.match: after
  • type: log
enabled: true paths: - /var/log/nftables/web.json fields: log_type: nftables_web fields_under_root: true json.keys_under_root: true json.add_error_key: true multiline.pattern: '^{' multiline.negate: true multiline.match: after processors:
  • add_host_metadata:
when.not.contains.tags: forwarded
  • add_docker_metadata: ~
  • add_kubernetes_metadata: ~

Elasticsearch output

output.elasticsearch: hosts: ["localhost:9200"] template.settings: index.number_of_shards: 1 index.number_of_replicas: 0 index: "nftables-logs-%{+yyyy.MM.dd}"

Kibana dashboard loading

setup.kibana: host: "localhost:5601"

Enable index lifecycle management

setup.ilm: enabled: true rollover_alias: "nftables-logs" pattern: "%{now/d}-000001" policy: "nftables-policy" logging.level: info logging.to_files: true logging.files: path: /var/log/filebeat name: filebeat keepfiles: 7 permissions: 0644

Enable and start all services

Enable nftables, start the exporter service, and configure Filebeat to begin log shipping.

sudo systemctl enable --now nftables
sudo systemctl enable --now nftables-exporter
sudo systemctl enable --now filebeat

Load nftables rules

sudo nft -f /etc/nftables.conf

Verify services are running

sudo systemctl status nftables nftables-exporter filebeat

Configure Prometheus to scrape nftables metrics

Add the nftables exporter as a scrape target in your Prometheus configuration.

# Add to scrape_configs section
scrape_configs:
  - job_name: 'nftables'
    static_configs:
      - targets: ['localhost:9090']
    scrape_interval: 15s
    scrape_timeout: 10s
    metrics_path: /metrics
    honor_labels: true
    params:
      format: ['prometheus']

  # Additional job for detailed nftables statistics
  - job_name: 'nftables-detailed'
    static_configs:
      - targets: ['localhost:9090']
    scrape_interval: 5s
    metrics_path: /metrics
    params:
      collect[]: ['nftables.security']
      

Global config

global: scrape_interval: 15s evaluation_interval: 15s external_labels: monitor: 'nftables-monitor'

Rule files for alerting

rule_files: - "nftables_alerts.yml"

Alertmanager configuration

alerting: alertmanagers: - static_configs: - targets: - alertmanager:9093

Create Prometheus alerting rules

Define alerting rules for nftables security events and anomalies.

groups:
  • name: nftables_security
rules: - alert: NFTablesHighDropRate expr: rate(nftables_packets_total{action="drop"}[5m]) > 100 for: 2m labels: severity: warning service: nftables annotations: summary: "High packet drop rate detected" description: "nftables is dropping {{ $value }} packets per second on {{ $labels.instance }}" - alert: NFTablesSSHBruteForce expr: rate(nftables_security_events_total{event_type="ssh_brute_force"}[1m]) > 0 for: 0m labels: severity: critical service: nftables annotations: summary: "SSH brute force attack detected" description: "SSH brute force attack from {{ $labels.source_ip }} detected on {{ $labels.instance }}" - alert: NFTablesPortScanDetected expr: rate(nftables_security_events_total{event_type="port_scan"}[5m]) > 10 for: 1m labels: severity: warning service: nftables annotations: summary: "Port scan activity detected" description: "Port scan from {{ $labels.source_ip }} detected on {{ $labels.instance }}" - alert: NFTablesExporterDown expr: up{job="nftables"} == 0 for: 1m labels: severity: critical service: nftables annotations: summary: "nftables exporter is down" description: "nftables Prometheus exporter has been down for more than 1 minute on {{ $labels.instance }}" - alert: NFTablesInvalidPackets expr: rate(nftables_security_events_total{event_type="invalid_packet"}[5m]) > 50 for: 2m labels: severity: warning service: nftables annotations: summary: "High rate of invalid packets" description: "{{ $value }} invalid packets per second detected on {{ $labels.instance }}" - alert: NFTablesConnectionTableFull expr: nftables_connections_active > 50000 for: 5m labels: severity: warning service: nftables annotations: summary: "Connection tracking table nearly full" description: "{{ $value }} active connections on {{ $labels.instance }}, approaching limit"

Create Grafana dashboard for nftables monitoring

Import a comprehensive Grafana dashboard configuration for visualizing nftables metrics and security events.

{
  "dashboard": {
    "id": null,
    "title": "NFTables Security Monitoring",
    "tags": ["nftables", "security", "firewall"],
    "timezone": "browser",
    "panels": [
      {
        "id": 1,
        "title": "Packet Processing Rate",
        "type": "graph",
        "targets": [
          {
            "expr": "rate(nftables_packets_total[5m])",
            "legendFormat": "{{action}} - {{protocol}}"
          }
        ],
        "yAxes": [
          {
            "label": "Packets/sec",
            "min": 0
          }
        ],
        "gridPos": {
          "h": 8,
          "w": 12,
          "x": 0,
          "y": 0
        }
      },
      {
        "id": 2,
        "title": "Security Events",
        "type": "stat",
        "targets": [
          {
            "expr": "sum(rate(nftables_security_events_total[5m])) by (event_type)",
            "legendFormat": "{{event_type}}"
          }
        ],
        "gridPos": {
          "h": 8,
          "w": 12,
          "x": 12,
          "y": 0
        }
      },
      {
        "id": 3,
        "title": "Top Blocked Source IPs",
        "type": "table",
        "targets": [
          {
            "expr": "topk(10, sum(rate(nftables_security_events_total[1h])) by (source_ip))",
            "format": "table",
            "instant": true
          }
        ],
        "gridPos": {
          "h": 8,
          "w": 24,
          "x": 0,
          "y": 8
        }
      },
      {
        "id": 4,
        "title": "Active Connections",
        "type": "singlestat",
        "targets": [
          {
            "expr": "nftables_connections_active"
          }
        ],
        "gridPos": {
          "h": 4,
          "w": 6,
          "x": 0,
          "y": 16
        }
      },
      {
        "id": 5,
        "title": "Rule Hit Rate",
        "type": "heatmap",
        "targets": [
          {
            "expr": "rate(nftables_rule_hits_total[5m])",
            "legendFormat": "{{rule_name}}"
          }
        ],
        "gridPos": {
          "h": 8,
          "w": 18,
          "x": 6,
          "y": 16
        }
      }
    ],
    "time": {
      "from": "now-1h",
      "to": "now"
    },
    "refresh": "5s"
  }
}

Configure Elasticsearch index template

Create Elasticsearch index template for nftables logs

Configure Elasticsearch with an optimized index template for nftables log data structure and retention.

curl -X PUT "localhost:9200/_index_template/nftables-logs" -H 'Content-Type: application/json' -d'
{
  "index_patterns": ["nftables-logs-*"],
  "template": {
    "settings": {
      "number_of_shards": 1,
      "number_of_replicas": 0,
      "index.lifecycle.name": "nftables-policy",
      "index.lifecycle.rollover_alias": "nftables-logs"
    },
    "mappings": {
      "properties": {
        "@timestamp": {
          "type": "date"
        },
        "hostname": {
          "type": "keyword"
        },
        "message": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        },
        "log_type": {
          "type": "keyword"
        },
        "severity": {
          "type": "keyword"
        },
        "source_ip": {
          "type": "ip"
        },
        "dest_ip": {
          "type": "ip"
        },
        "source_port": {
          "type": "integer"
        },
        "dest_port": {
          "type": "integer"
        },
        "protocol": {
          "type": "keyword"
        },
        "action": {
          "type": "keyword"
        }
      }
    }
  }
}'

Create Kibana index pattern and visualizations

Set up Kibana with index patterns and basic visualizations for nftables log analysis.

# Create index pattern
curl -X POST "localhost:5601/api/saved_objects/index-pattern/nftables-logs-*" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d'
{
  "attributes": {
    "title": "nftables-logs-*",
    "timeFieldName": "@timestamp",
    "fields": "[{\"name\":\"@timestamp\",\"type\":\"date\",\"searchable\":true,\"aggregatable\":true}]"
  }
}'

Restart services to apply all configurations

sudo systemctl restart prometheus filebeat

Verify your setup

# Check nftables rules are loaded
sudo nft list ruleset

Verify logging is working

sudo tail -f /var/log/nftables/general.log

Check Prometheus metrics are available

curl http://localhost:9090/metrics | grep nftables

Test security detection with port scan simulation

nmap -sS -O localhost

Check security logs for detected scan

sudo tail /var/log/nftables/security.log

Verify Elasticsearch is receiving logs

curl -X GET "localhost:9200/nftables-logs-*/_search?pretty&size=5"

Check service status

sudo systemctl status nftables nftables-exporter filebeat
Note: After configuration, it may take a few minutes for metrics to appear in Prometheus and logs to flow to Elasticsearch. Monitor the service logs for any configuration errors.

Common issues

SymptomCauseFix
No metrics in PrometheusExporter not running or port blockedCheck sudo systemctl status nftables-exporter and firewall rules
Logs not reaching ElasticsearchFilebeat configuration errorCheck sudo journalctl -u filebeat and verify Elasticsearch connectivity
High CPU usage from loggingToo verbose logging rulesAdd rate limiting to log rules: limit rate 10/minute
Permission denied on log filesIncorrect log directory permissionsRun sudo chown syslog:adm /var/log/nftables and restart rsyslog
nftables rules not persistentRules not saved to config fileSave rules with sudo nft list ruleset > /etc/nftables.conf
Grafana dashboard emptyPrometheus not scraping metricsCheck Prometheus targets at http://localhost:9090/targets

Next steps

Running this in production?

Want this handled for you? Running advanced firewall monitoring at scale adds a second layer of work: capacity planning for log volume, alert tuning to prevent fatigue, and 24/7 response to security events. See how we run infrastructure like this for European teams with automated threat response and compliance reporting.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle infrastructure security hardening for businesses that depend on uptime. From initial setup to ongoing operations.