Freorit

Open Source & Security Practitioner
Security Monitoring OpenBSD PF Prometheus Grafana ca. 15 min read Intermediate Updated: 2025

Firewall Monitoring with Prometheus & Grafana

Build complete visibility into your OpenBSD PF firewall by centralizing metrics and logs into Prometheus and Grafana. This guide shows how to transform raw firewall data into actionable security insights.

Why Monitor Your Firewall?

A firewall blocks traffic. That's important. But what's equally important is knowing what it's blocking and when.

Without monitoring, you only see the outcome: "Connection refused." With monitoring, you see the pattern: "50 SSH attempts per minute from this IP, escalating over 2 hours." That's actionable.

The Problem

OpenBSD's PF firewall is excellent at filtering. But the default setup leaves critical questions unanswered:

Without central monitoring, answering these questions means SSH-ing to the router and manually inspecting logs. That doesn't scale.

The Solution

This guide shows how to build a monitoring stack that transforms your firewall from a silent guardian into a communicative one. The result:

Who This Is For

You have an OpenBSD router or firewall. You want to see what it's doing without manually parsing logs. You're willing to set up a small monitoring stack (Prometheus + Loki + Grafana) to gain that visibility.

Architecture Overview

The monitoring stack has two data flows: metrics and logs.

Key Components

Component Role Data Source
OpenBSD PF Data source: firewall metrics and logs snmpd (SNMP), syslog
SNMP Exporter Converts SNMP to Prometheus format Router SNMP queries
Prometheus Time-series metrics database SNMP Exporter scrapes
Syslog-ng / Grafana Alloy Log collection and shipping Router syslog, PF logs
Loki Log aggregation and indexing Grafana Alloy ships logs
Grafana Visualization and alerting Queries Prometheus + Loki

Data Flows

Metrics Flow:

OpenBSD snmpd (port 161) | v (SNMP queries every 15s) SNMP Exporter (converts to Prometheus format) | v (metrics exposed on port 9116) Prometheus (scrapes every 30s) | v (queries from Grafana) Grafana Dashboards

Logs Flow:

PF Firewall -> syslogd (UDP 514) Grafana Alloy (reads logs and ships to Loki) | v (sends to Loki) Loki (indexes and stores) | v (queries from Grafana) Grafana Dashboards & Alerts

Layer 1: Hardened PF Firewall

The firewall itself must be configured to collect and export useful metrics. Three key aspects:

Source Tracking for Attack Detection

Source tracking allows PF to monitor connection patterns per IP address. This is essential for detecting brute force and port scans.

/etc/pf.conf
# Enable source tracking
set limit src-nodes 20000

# Dynamic blocking tables
table <bruteforce> persist
table <port_scanners> persist

# SSH brute force protection
pass in on pppoe0 proto tcp to port 22 \
keep state (max-src-conn 3, max-src-conn-rate 3/60, \
        source-track rule, overload <bruteforce> flush global)

# Block detected attackers
block quick from <bruteforce>
    
How It Works

When an IP attempts more than 3 SSH connections within 60 seconds, PF automatically adds it to the <bruteforce> table. All future traffic from that IP is blocked. This happens in real-time, without any user intervention.

Rate Limiting

Beyond brute force protection, rate limiting prevents resource exhaustion:

/etc/pf.conf
# General outbound rate limiting
pass in on re0 from (re0:network) to any \
keep state (max-src-conn 100, max-src-conn-rate 50/10)

# Global WAN limit
pass out on pppoe0 proto tcp \
keep state (max 10000, max-src-conn-rate 100/10)
    

Anti-Spoofing

Prevent IP spoofing attacks and filter out invalid source addresses:

/etc/pf.conf
# Prevent spoofed packets
antispoof quick for pppoe0
antispoof quick for re0

# Block invalid private ranges
table <martians> const { 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8 }
block in quick on pppoe0 from <martians> to any
    

Enable SNMP Metrics Export

OpenBSD's SNMP daemon (snmpd) exports firewall metrics that Prometheus can scrape:

/etc/snmpd.conf
listen on 127.0.0.1
listen on 10.20.1.1  # Make accessible from monitoring host

# Allow read-only access from monitoring network
read-only community "public" 10.20.1.0/24
    

Key SNMP metrics PF exposes:

Enable snmpd to start on boot:

Terminal
rcctl enable snmpd
rcctl start snmpd
    

Layer 2: Centralized Monitoring

The monitoring stack aggregates metrics and logs into Prometheus and Loki, then visualizes them in Grafana.

Prometheus Setup: Metrics Collection

Install the SNMP Exporter (converts SNMP to Prometheus format):

Terminal
docker run -d \
  --name snmp-exporter \
  -p 9116:9116 \
  prometheuscommunity/snmp-exporter
    

Configure Prometheus to scrape both SNMP and node metrics:

prometheus.yml
global:
  scrape_interval: 30s

scrape_configs:
  - job_name: 'snmp-router'
static_configs:
  - targets: ['10.20.0.1']
metrics_path: /snmp
params:
  module: [if_mib]
relabel_configs:
  - source_labels: [__address__]
target_label: __param_target
  - source_labels: [__param_target]
target_label: instance
  - target_label: __address__
replacement: localhost:9116
    

Loki Setup: Log Aggregation

Loki ingests logs from syslog-ng via Grafana Alloy. Configuration:

loki-config.yml
auth_enabled: false

ingester:
  chunk_idle_period: 3m
  max_chunk_age: 1h
  max_streams_per_user: 10000

schema_config:
  configs:
- from: 2025-01-01
  store: tsdb
  object_store: filesystem
  schema: v13
  index:
prefix: index_
period: 24h

storage_config:
  filesystem:
directory: /loki/chunks
    

Grafana Dashboards

Grafana visualizes the collected data. Key dashboard panels:

Security Overview

Firewall Health

Traffic Analysis

Alert Rules

Define alerts for anomalies in Prometheus:

alerts.yml
groups:
  - name: firewall_alerts
rules:
  - alert: StateTableNearFull
expr: (pfStateCount / pfStateLimit) > 0.8
for: 5m
annotations:
  summary: "Firewall state table >80% full"

  - alert: BruteForceSpike
expr: rate(pfTableCount{table="bruteforce"}[5m]) > 1
for: 2m
annotations:
  summary: "Brute force spike detected"

  - alert: HighBlockRate
expr: rate(pfBlockPackets[5m]) > 100
for: 5m
annotations:
  summary: "Unusually high block rate"
    
Verification: After setup, open Grafana and query Prometheus. You should see:
  • Live state table count
  • Traffic rates in/out
  • Historical trends over the past hours/days

Attack Detection in Action

Example 1: SSH Brute Force Attack

Scenario: Attacker attempts rapid SSH login attempts.

1

Attempt 1-3: Allowed

Attacker makes first 3 SSH connections within the allowed rate. Firewall state table records these as established connections.

2

Attempt 4: Rate Limit Triggered

Fourth connection attempt within 60 seconds. The rate limit rule triggers: max-src-conn-rate 3/60 exceeded.

3

Automatic Action: IP Added to Table

PF executes overload <bruteforce>: the attacking IP is automatically added to the <bruteforce> table and all connections from this IP are terminated (flush global).

4

Block: Future Traffic Dropped

Rule block quick from <bruteforce> drops all future packets from the blocked IP. No more responses sent.

Timeline

Total detection time: 10-15 seconds from initial attack

Monitoring visibility:

Example 2: Port Scan Detection

Scenario: Attacker runs nmap -p- <router-ip> to scan all ports.

What Happens:

  1. Rapid SYN packets to many ports from single IP
  2. PF source tracking records high connection rate from this IP
  3. Rate limit rule: max-src-conn-rate 50/10 (50 connections per 10 seconds) exceeded
  4. IP added to <port_scanners> table
  5. All future traffic from IP blocked with block quick from <port_scanners>

Monitoring detection:

Prometheus query to detect port scans:

PromQL
rate(pfTableCount{table="port_scanners"}[5m]) > 0.1
    

This alerts when new IPs are being added to the port scanners table at a rate > 1 per 50 seconds.

Results & Metrics

Quantitative Improvements

Metric Before Monitoring After Monitoring
Visibility into attack patterns Manual log inspection only Real-time dashboards
Time to detect anomaly Hours (if noticed at all) Seconds (automated alerts)
Historical trending None 30+ days of metrics
Capacity planning data Guesswork Peak usage trends visible

Real-World Example: First Week

After deploying monitoring, the first week revealed:

Key Insight

Without monitoring, you'd have no visibility into these attacks. With monitoring, you see the patterns, understand the baseline, and can confidently tune your rules.

Best Practices

1. Start Conservative with Limits

Don't deploy tight rate limits immediately. Tune gradually:

2. Whitelist Trusted IPs

Create allowlists for known-good sources (your office, home devices, trusted partners):

/etc/pf.conf
table <trusted> persist file "/etc/pf-trusted.txt"

# Higher limits for trusted sources
pass in on pppoe0 proto tcp from <trusted> to port 22 \
keep state (max-src-conn 20, max-src-conn-rate 20/60)

# Strict limits for everyone else
pass in on pppoe0 proto tcp to port 22 \
keep state (max-src-conn 3, max-src-conn-rate 3/60, \
        overload <bruteforce> flush global)
    

3. Monitor the Monitors

Add alerts for monitoring infrastructure failures:

4. Log Volume Control

Excessive logging fills disk quickly. Be selective:

/etc/pf.conf
# Only log state-establishing packets (default)
pass out log on pppoe0 proto tcp

# NOT: pass out log (all) on pppoe0  <- Creates 10GB/day of logs
    

5. Backup Your Configurations

Keep copies of working pf.conf, snmpd.conf, and Prometheus rules:

Terminal
cp /etc/pf.conf /etc/pf.conf.backup.$(date +%Y%m%d)
git add -A && git commit -m "Firewall config backup"
    

Lessons Learned

Technical Lessons

Operational Lessons

Monitoring Lessons

Open Source Projects

This guide is built entirely on open source: