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

SettingValue
EnabledChecked
IPS modeChecked (blocks threats, not just alerts)
Promiscuous modeUnchecked
InterfacesWAN, DMZ (select interfaces to monitor)
Pattern matcherHyperscan
Enable syslog alertsChecked
Enable eve syslog outputChecked
Rotate logWeekly

Download Tab

Enable these rulesets:

RulesetPurpose
ET open/emerging-exploitExploit detection
ET open/emerging-malwareMalware signatures
ET open/emerging-scanPort scan detection
ET open/emerging-web_serverWeb attack protection
ET open/emerging-dosDoS detection
ET open/threatview_CS_c2Command & control detection
ET open/emerging-phishingPhishing attempts
ET open/emerging-sqlSQL 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:

FieldValue
EnabledChecked
TransportUDP(4)
ApplicationsSelect All (or at minimum: filterlog, suricata)
LevelsSelect All (critical - Suricata alerts use various levels)
FacilitiesSelect All
Hostname192.168.2.221 (your Promtail LoadBalancer IP)
Port514
RFC5424Checked (required for Promtail parsing)

Save and Apply.

Critical Settings

Two settings caused issues during setup:

  1. 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-999
    
  2. All 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

  1. Verify “Enable syslog alerts” is checked in IDS settings
  2. Verify all log levels are selected in the syslog destination
  3. 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.

OPNsense IDS Dashboard

Files