OPNsense includes Suricata for intrusion detection, but the built-in alerts page provides limited visibility. This post covers forwarding IDS alerts to Loki via syslog and visualizing them in Grafana alongside firewall logs.
Architecture
┌─────────────────┐ UDP/514 ┌──────────────────┐
│ OPNsense │ RFC5424 │ Promtail │
│ ┌───────────┐ │ ───────────────▶ │ (syslog recv) │
│ │ Suricata │ │ │ 192.168.2.221 │
│ │ filterlog │ │ └────────┬─────────┘
│ └───────────┘ │ │
└─────────────────┘ ▼
┌──────────────────┐
│ Loki │
│ (log storage) │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Grafana │
│ (dashboards) │
└──────────────────┘
Prerequisites
- OPNsense firewall with Suricata IDS enabled
- Kubernetes cluster with Loki deployed
- MetalLB or NodePort for exposing the syslog receiver
Step 1: Enable Suricata IDS on OPNsense
Navigate to Services → Intrusion Detection → Administration.
Settings Tab
| Setting | Value |
|---|---|
| Enabled | Checked |
| IPS mode | Checked (blocks threats, not just alerts) |
| Promiscuous mode | Unchecked |
| Interfaces | WAN, DMZ (select interfaces to monitor) |
| Pattern matcher | Hyperscan |
| Enable syslog alerts | Checked |
| Enable eve syslog output | Checked |
| Rotate log | Weekly |
Download Tab
Enable these rulesets:
| Ruleset | Purpose |
|---|---|
| ET open/emerging-exploit | Exploit detection |
| ET open/emerging-malware | Malware signatures |
| ET open/emerging-scan | Port scan detection |
| ET open/emerging-web_server | Web attack protection |
| ET open/emerging-dos | DoS detection |
| ET open/threatview_CS_c2 | Command & control detection |
| ET open/emerging-phishing | Phishing attempts |
| ET open/emerging-sql | SQL injection |
Click Download & Update Rules after selecting.
Step 2: Deploy Promtail Syslog Receiver
Create a dedicated Promtail deployment to receive syslog from OPNsense:
# promtail-syslog.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: promtail-syslog-config
namespace: monitoring
data:
promtail.yaml: |
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: syslog
syslog:
listen_address: 0.0.0.0:514
listen_protocol: udp
idle_timeout: 60s
label_structured_data: yes
labels:
job: syslog
relabel_configs:
- source_labels: ['__syslog_message_hostname']
target_label: 'host'
- source_labels: ['__syslog_message_hostname']
target_label: 'hostname'
- source_labels: ['__syslog_message_severity']
target_label: 'severity'
- source_labels: ['__syslog_message_facility']
target_label: 'facility'
- source_labels: ['__syslog_message_app_name']
target_label: 'app'
pipeline_stages:
- static_labels:
source: opnsense
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: promtail-syslog
namespace: monitoring
spec:
replicas: 1
selector:
matchLabels:
app: promtail-syslog
template:
metadata:
labels:
app: promtail-syslog
spec:
containers:
- name: promtail
image: grafana/promtail:2.9.3
args:
- -config.file=/etc/promtail/promtail.yaml
ports:
- name: http-metrics
containerPort: 9080
- name: syslog-udp
containerPort: 514
protocol: UDP
volumeMounts:
- name: config
mountPath: /etc/promtail
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
volumes:
- name: config
configMap:
name: promtail-syslog-config
---
apiVersion: v1
kind: Service
metadata:
name: promtail-syslog
namespace: monitoring
spec:
type: LoadBalancer
loadBalancerIP: 192.168.2.221 # Adjust for your MetalLB pool
externalTrafficPolicy: Local
ports:
- name: syslog-udp
port: 514
targetPort: 514
protocol: UDP
- name: http-metrics
port: 9080
targetPort: 9080
selector:
app: promtail-syslog
Apply:
kubectl apply -f promtail-syslog.yaml
Verify the service got an external IP:
kubectl get svc promtail-syslog -n monitoring
Step 3: Configure OPNsense Syslog Destination
Navigate to System → Settings → Logging / Targets and add a new destination:
| Field | Value |
|---|---|
| Enabled | Checked |
| Transport | UDP(4) |
| Applications | Select All (or at minimum: filterlog, suricata) |
| Levels | Select All (critical - Suricata alerts use various levels) |
| Facilities | Select All |
| Hostname | 192.168.2.221 (your Promtail LoadBalancer IP) |
| Port | 514 |
| RFC5424 | Checked (required for Promtail parsing) |
Save and Apply.
Critical Settings
Two settings caused issues during setup:
RFC5424 must be enabled - Promtail expects RFC5424 syslog format. Without this, you’ll see parsing errors:
error parsing syslog stream: expecting a version value in the range 1-999All log levels must be selected - Suricata alerts use various severity levels. Selecting only “info” filters out actual alerts.
Step 4: Create Grafana Dashboard
Create a ConfigMap with the dashboard JSON (Grafana’s sidecar will auto-load it):
apiVersion: v1
kind: ConfigMap
metadata:
name: opnsense-grafana-dashboard
namespace: monitoring
labels:
grafana_dashboard: "1"
data:
opnsense-ids-dashboard.json: |
{
"title": "OPNsense IDS & Firewall",
"uid": "opnsense-ids",
...
}
The full dashboard JSON is available in the k8s-configs repository.
Key panels include:
- IDS Alerts by Severity - Pie chart of alert distribution
- Total IDS Alerts - Counter for the selected time range
- OPNsense Events Over Time - Stacked bar chart by application
- Recent IDS Alerts - Log panel with EVE JSON details
- Recent Firewall Events - filterlog entries
Dashboard Queries
IDS alerts:
{source="opnsense", app="suricata"}
Firewall events:
{source="opnsense", app="filterlog"}
Alert count by severity:
sum by (severity) (count_over_time({source="opnsense", app="suricata"} [$__range]))
Step 5: Verify the Pipeline
Check that logs are reaching Loki:
# List all apps from OPNsense
kubectl exec -n monitoring loki-0 -c loki -- \
wget -qO- 'http://localhost:3100/loki/api/v1/series?match[]={source="opnsense"}' | \
jq -r '.data[].app' | sort -u
Expected output:
configd.py
filterlog
haproxy
kea-dhcp4
suricata
Query recent Suricata alerts:
kubectl exec -n monitoring loki-0 -c loki -- \
wget -qO- 'http://localhost:3100/loki/api/v1/query_range?query={source="opnsense",app="suricata"}&limit=3' | \
jq -r '.data.result[0].values[][1]' | head -1
Troubleshooting
No suricata logs appearing
- Verify “Enable syslog alerts” is checked in IDS settings
- Verify all log levels are selected in the syslog destination
- Check Promtail logs for parsing errors:
kubectl logs -n monitoring deploy/promtail-syslog -c promtail --tail=20
Parsing errors in Promtail
Enable RFC5424 in the OPNsense syslog destination. BSD syslog format (default) is not supported.
Dashboard shows “No data”
Check the Loki datasource UID in the dashboard JSON matches your Grafana configuration:
kubectl exec -n monitoring deploy/prometheus-grafana -- \
curl -s 'http://localhost:3000/api/datasources' | \
jq -r '.[] | select(.type=="loki") | {name, uid}'
Update the dashboard JSON to use the correct UID.
Flowbit warnings in Suricata
flowbit 'ET.ErlangOTPBanner' is checked but not set
These warnings are harmless. They occur when rules check for flowbits that other (disabled) rules would set. The detection chains won’t fire, but standalone rules work correctly.
Result
The Grafana dashboard provides visibility into:
- SSH scan attempts from internet scanners
- Web attack probes (SQL injection, webshells, path traversal)
- Malware communication attempts
- Port scanning activity
Within minutes of enabling IDS, alerts from internet background noise appeared - SSH scans, web vulnerability probes, and other automated attacks that constantly probe public IPs.
