12 KiB
| title | description | published | date | tags | editor | dateCreated |
|---|---|---|---|---|---|---|
| OpnSense - NTFY Integration | Security Notifications | true | 2026-02-23T22:00:46.462Z | markdown | 2026-02-23T22:00:37.268Z |
OPNsense ntfy Alerts
Service: ntfy push notifications from OPNsense Host: OPNsense firewall ntfy Server: Your self-hosted ntfy instance on Netgrimoire Methods: CrowdSec HTTP plugin · Monit custom script · Suricata EVE watcher
Overview
OPNsense does not have a built-in ntfy notification channel, but there are three distinct integration points that together provide complete coverage:
| Method | What It Alerts On | Priority |
|---|---|---|
| CrowdSec HTTP plugin | Every IP ban decision CrowdSec makes | 🔴 Best for threat intel alerts |
| Monit + curl script | System health, service failures, Suricata EVE matches, login failures | 🔴 Best for operational alerts |
| Suricata EVE watcher | Suricata high-severity IDS hits (via Monit watching eve.json) | 🟡 Covered via Monit |
All three use your self-hosted ntfy instance. None require external services.
Prerequisites
Before starting, confirm:
- ntfy is running and reachable at
https://ntfy.netgrimoire.com(or your internal URL) - ntfy topic created: e.g.
opnsense-alerts - If ntfy has auth enabled, have a token ready
- SSH access to OPNsense as root
Method 1 — CrowdSec HTTP Notification Plugin
This is the cleanest integration for security alerts. CrowdSec has a built-in HTTP notification plugin. Every time it makes a ban decision — whether from community intel, a Suricata match passed through CrowdSec, or a brute-force detection — it POSTs to ntfy.
Step 1 — Create the HTTP notification config
SSH into OPNsense and create the ntfy config file:
ssh root@192.168.3.4
cat > /usr/local/etc/crowdsec/notifications/ntfy.yaml << 'EOF'
# ntfy notification plugin for CrowdSec
# CrowdSec uses its built-in HTTP plugin pointed at ntfy
type: http
name: ntfy_default
log_level: info
# ntfy accepts plain POST body as the notification message
# format is a Go template — .[]Alert is the list of alerts
format: |
{{range .}}
🚨 CrowdSec Decision
Scenario: {{.Scenario}}
Attacker IP: {{.Source.IP}}
Country: {{.Source.Cn}}
Action: {{.Decisions | len}} x {{(index .Decisions 0).Type}}
Duration: {{(index .Decisions 0).Duration}}
{{end}}
url: https://ntfy.netgrimoire.com/opnsense-alerts
method: POST
headers:
Title: "CrowdSec Ban — OPNsense"
Priority: "high"
Tags: "rotating_light,shield"
# Uncomment and set token if ntfy auth is enabled:
# Authorization: "Bearer YOUR_NTFY_TOKEN"
# skip_tls_verify: false
EOF
⚠ Replace
https://ntfy.netgrimoire.com/opnsense-alertswith your actual ntfy URL and topic. If ntfy is internal-only and OPNsense can reach it by hostname, the internal URL works fine.
Step 2 — Register the plugin in profiles.yaml
Edit the CrowdSec profiles file to dispatch decisions to the ntfy plugin:
vi /usr/local/etc/crowdsec/profiles.yaml
Find the notifications: section of the default profile and add ntfy_default:
name: default_ip_remediation
filters:
- Alert.Remediation == true && Alert.GetScope() == "Ip"
decisions:
- type: ban
duration: 4h
notifications:
- ntfy_default # ← add this line
on_success: break
✓ The
ntfy_defaultname must match thename:field in the yaml file you created above exactly.
Step 3 — Set correct file ownership
CrowdSec rejects plugins if the configuration file is not owned by the root user and root group. Ensure the file has the right permissions:
chown root:wheel /usr/local/etc/crowdsec/notifications/ntfy.yaml
chmod 600 /usr/local/etc/crowdsec/notifications/ntfy.yaml
Step 4 — Restart CrowdSec and test
# Restart via OPNsense service manager (do NOT use systemctl/service directly)
# Go to: Services → CrowdSec → Settings → Apply
# Or from shell:
pluginctl -s crowdsec restart
Test by sending a manual notification:
cscli notifications test ntfy_default
You should receive a test push on your device within a few seconds.
Then trigger a real decision to verify the full pipeline:
# Ban your own IP for 2 minutes as a test (replace with your IP)
cscli decisions add -t ban -d 2m -i 1.2.3.4
# Watch for ntfy notification
# Remove the test ban:
cscli decisions delete -i 1.2.3.4
Method 2 — Monit + curl Script
Monit is OPNsense's built-in service monitor. It can watch processes, files, system resources, and log patterns — and call a custom shell script when a condition is met. The script fires a curl POST to ntfy.
This covers things CrowdSec doesn't — service failures, high CPU, gateway down events, SSH login failures, disk usage, and Suricata EVE alerts.
Step 2.1 — Create the ntfy alert script
cat > /usr/local/bin/ntfy-alert.sh << 'EOF'
#!/usr/local/bin/bash
# ntfy-alert.sh — called by Monit to send ntfy push notifications
# Monit provides variables: $MONIT_HOST, $MONIT_SERVICE,
# $MONIT_DESCRIPTION, $MONIT_EVENT
NTFY_URL="https://ntfy.netgrimoire.com/opnsense-alerts"
# NTFY_TOKEN="Bearer YOUR_NTFY_TOKEN" # uncomment if ntfy auth enabled
TITLE="${MONIT_HOST}: ${MONIT_SERVICE}"
MESSAGE="${MONIT_EVENT} — ${MONIT_DESCRIPTION}"
# Map Monit event types to ntfy priorities
case "$MONIT_EVENT" in
*"does not exist"*|*"failed"*|*"error"*)
PRIORITY="urgent"
TAGS="rotating_light,red_circle"
;;
*"changed"*|*"match"*)
PRIORITY="high"
TAGS="warning,yellow_circle"
;;
*"recovered"*|*"succeeded"*)
PRIORITY="default"
TAGS="white_check_mark,green_circle"
;;
*)
PRIORITY="default"
TAGS="bell"
;;
esac
curl -s \
-H "Title: ${TITLE}" \
-H "Priority: ${PRIORITY}" \
-H "Tags: ${TAGS}" \
-d "${MESSAGE}" \
"${NTFY_URL}"
# Uncomment for auth:
# curl -s \
# -H "Authorization: ${NTFY_TOKEN}" \
# -H "Title: ${TITLE}" \
# -H "Priority: ${PRIORITY}" \
# -H "Tags: ${TAGS}" \
# -d "${MESSAGE}" \
# "${NTFY_URL}"
EOF
chmod +x /usr/local/bin/ntfy-alert.sh
Step 2.2 — Enable Monit
Navigate to Services → Monit → Settings → General Settings
| Setting | Value |
|---|---|
| Enabled | ✓ |
| Polling Interval | 30 seconds |
| Start Delay | 120 seconds |
| Mail Server | Leave blank (using script instead) |
Click Save.
Step 2.3 — Add Service Tests
Navigate to Services → Monit → Service Tests Settings and add the following tests:
Test 1 — Custom Alert via Script
| Field | Value |
|---|---|
| Name | ntfy_alert |
| Condition | failed |
| Action | Execute |
| Path | /usr/local/bin/ntfy-alert.sh |
This is the reusable action that all other tests will invoke.
Test 2 — Suricata EVE High Alert
| Field | Value |
|---|---|
| Name | SuricataHighAlert |
| Condition | content = "\"severity\":1" |
| Action | Execute → /usr/local/bin/ntfy-alert.sh |
This watches for severity 1 (highest) alerts written to the Suricata EVE JSON log.
Test 3 — Suricata Process Down
| Field | Value |
|---|---|
| Name | SuricataRunning |
| Condition | failed |
| Action | Execute → /usr/local/bin/ntfy-alert.sh |
Test 4 — CrowdSec Process Down
| Field | Value |
|---|---|
| Name | CrowdSecRunning |
| Condition | failed |
| Action | Execute → /usr/local/bin/ntfy-alert.sh |
Test 5 — SSH Login Failure
| Field | Value |
|---|---|
| Name | SSHFailedLogin |
| Condition | content = "Failed password" |
| Action | Execute → /usr/local/bin/ntfy-alert.sh |
Test 6 — OPNsense Web UI Login Failure
| Field | Value |
|---|---|
| Name | WebUILoginFail |
| Condition | content = "webgui" |
| Action | Execute → /usr/local/bin/ntfy-alert.sh |
Step 2.4 — Add Service Monitors
Navigate to Services → Monit → Service Settings and add:
Monitor 1 — Suricata EVE Log (high alerts)
| Field | Value |
|---|---|
| Name | SuricataEVE |
| Type | File |
| Path | /var/log/suricata/eve.json |
| Tests | SuricataHighAlert |
Monitor 2 — Suricata Process
| Field | Value |
|---|---|
| Name | Suricata |
| Type | Process |
| PID File | /var/run/suricata.pid |
| Tests | SuricataRunning |
| Restart Method | /usr/local/etc/rc.d/suricata restart |
Monitor 3 — CrowdSec Process
| Field | Value |
|---|---|
| Name | CrowdSec |
| Type | Process |
| Match | crowdsec |
| Tests | CrowdSecRunning |
Monitor 4 — SSH Auth Log
| Field | Value |
|---|---|
| Name | SSHAuth |
| Type | File |
| Path | /var/log/auth.log |
| Tests | SSHFailedLogin |
Monitor 5 — System Resources (optional)
| Field | Value |
|---|---|
| Name | System |
| Type | System |
| Tests | ntfy_alert (on resource threshold exceeded) |
Click Apply after adding all services.
Step 2.5 — Test Monit alerts
# Manually invoke the script to test ntfy connectivity
MONIT_HOST="OPNsense" \
MONIT_SERVICE="Test" \
MONIT_EVENT="Test alert" \
MONIT_DESCRIPTION="Testing ntfy integration from Monit" \
/usr/local/bin/ntfy-alert.sh
You should receive a push notification immediately.
Alert Topics & Priority Mapping
Consider using separate ntfy topics to filter notifications by type on your device:
| Topic | Used For | Suggested ntfy Priority |
|---|---|---|
opnsense-alerts |
CrowdSec bans, Suricata high hits | high / urgent |
opnsense-health |
Monit service failures, process restarts | high |
opnsense-info |
Service recoveries, status changes | default / low |
To use separate topics, change the NTFY_URL in the Monit script and the url: in the CrowdSec config accordingly.
ntfy Priority Reference
ntfy supports five priority levels that map to different notification behaviors on Android/iOS:
| ntfy Priority | Numeric | Behavior |
|---|---|---|
min |
1 | No notification, no sound |
low |
2 | Notification, no sound |
default |
3 | Notification with sound |
high |
4 | Notification with sound, bypasses DND |
urgent |
5 | Phone rings through DND, repeated |
For firewall alerts: use urgent for process failures and high for IDS/ban events. Reserve urgent sparingly to avoid alert fatigue.
Keeping Config Persistent Across Upgrades
OPNsense upgrades can overwrite files in certain paths. The safest locations for persistent custom files:
| File | Location | Persistent? |
|---|---|---|
| ntfy-alert.sh | /usr/local/bin/ntfy-alert.sh |
✓ Yes — not touched by upgrades |
| CrowdSec ntfy.yaml | /usr/local/etc/crowdsec/notifications/ntfy.yaml |
✓ Yes — plugin config directory |
| CrowdSec profiles.yaml | /usr/local/etc/crowdsec/profiles.yaml |
⚠ Re-check after CrowdSec updates |
After any OPNsense or CrowdSec update, verify:
# Check CrowdSec notification config is still present
ls -la /usr/local/etc/crowdsec/notifications/
# Test CrowdSec ntfy still works
cscli notifications test ntfy_default
# Check Monit script is still executable
ls -la /usr/local/bin/ntfy-alert.sh
Troubleshooting
No notification received from CrowdSec test:
# Check CrowdSec logs for plugin errors
tail -50 /var/log/crowdsec.log | grep -i ntfy
tail -50 /var/log/crowdsec.log | grep -i notification
# Verify ntfy URL is reachable from OPNsense
curl -v -d "test" https://ntfy.netgrimoire.com/opnsense-alerts
# Check profiles.yaml has ntfy_default in notifications section
grep -A5 "notifications:" /usr/local/etc/crowdsec/profiles.yaml
No notification received from Monit:
# Run the script manually with test variables
MONIT_HOST="test" MONIT_SERVICE="test" \
MONIT_EVENT="test" MONIT_DESCRIPTION="test message" \
/usr/local/bin/ntfy-alert.sh
# Check Monit is running
ps aux | grep monit
# Check Monit logs
tail -50 /var/log/monit.log
CrowdSec plugin ownership error:
# Fix ownership if CrowdSec refuses to load the plugin
chown root:wheel /usr/local/etc/crowdsec/notifications/ntfy.yaml
ls -la /usr/local/etc/crowdsec/notifications/
ntfy auth failing:
# Test with token manually
curl -H "Authorization: Bearer YOUR_TOKEN" \
-H "Title: Test" \
-d "Auth test" \
https://ntfy.netgrimoire.com/opnsense-alerts
Related Documentation
- OPNsense Firewall — parent firewall documentation
- CrowdSec — threat intelligence engine sending these alerts
- Suricata IDS/IPS — source of EVE alerts watched by Monit
- ntfy — self-hosted notification server on Netgrimoire