Back in the early days of smartphones, Apple took the branding path of adding a lowercase ‘i’ to the front of everything. iPods, iPads, iPhones, iMacs – there became a proliferation of additional vowels for no good reason I could ever quite fathom.
It felt like an attempt at a bit of counter-culture, as everyone else was on a disemvoweling run – remember Flickr, twttr (oh the heady days!), Tumblr, and Scribd?
Anyway, the reason why I’m on this current reminisce, is because I was looking for a way to receive Nagios notifications on my phone nice & easily, something like an SMS (acronym, not disemvoweling in this case) – but without the associated costs, or a mobile app. I honestly toyed for a while with the idea of writing my own Android app, and learning yet another set of programming languages and protocols – and then I randomly came across ntfy.sh – and everything was suddenly SO. MUCH. SIMPLER!
The docker-compose.yaml file for setting up my own personal ntfy.sh server is insanely simple:
services:
ntfy:
container_name: ntfy
hostname: ntfy
image: binwiederhier/ntfy
command:
- serve
environment:
- TZ=Pacific/Auckland
volumes:
- ./var-cache-ntfy:/var/cache/ntfy
- ./etc-ntfy:/etc/ntfy
ports:
- 8000:80
restart: unless-stopped
And that was it done. The ntfy.sh Android app is small, and does exactly what I need (aforementioned iOS devices have their own one – but I can’t comment on that version) – which is to alert me to new notifications as soon as they arrive.
The next step was to write a Bash script to handle Nagios notifications and feed them out to my new ntfy.sh server. This one’s a bit bigger than others I’ve shared so far, because it’s doing a few things to make things pretty at the receiving end:
#!/usr/bin/env bash
# notify_via_ntfy.sh — Nagios script for sending alerts to Nagios
# Nagios macros available; https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/macrolist.html
#
# -----------------------------------------------------------------------------
# Description:
# This script takes a generated notification from Nagios, and sends it to a
# ntfy.sh endpoint, while auto-tagging with tags, emoji, and priority based
# on the values passed.
#
# Example usage:
# ./notify_via_ntfy.sh \
# --notification-type "TEST" \
# --state "TEST" \
# --host "Example hostname" \
# --service "Test service description"
# --endpoint "http://127.0.0.1:8000/nagiosNotifications" \
# --output "Test output" \
# --timestamp "1970-01-01T00:00:00+0"
#
# Example commands.cfg definition
# define command {
# command_name notify-host-by-ntfy
# command_line $USER2$/notify_via_ntfy.sh \
# --notification-type "$NOTIFICATIONTYPE$" \
# --state "$HOSTSTATE$" \
# --host "$HOSTNAME$" \
# --endpoint "$USER3$" \
# --output "$HOSTOUTPUT$ $LONGHOSTOUTPUT$" \
# --timestamp "$LONGDATETIME$"
# }
# define command {
# command_name notify-service-by-ntfy
# command_line $USER2$/notify_via_ntfy.sh \
# --notification-type "$NOTIFICATIONTYPE$" \
# --state "$SERVICESTATE$" \
# --host "$HOSTNAME$" \
# --service "$SERVICEDESC$" \
# --endpoint "$USER3$" \
# --output "$SERVICEOUTPUT$ $LONGSERVICEOUTPUT$" \
# --timestamp "$LONGDATETIME$"
# }
#
# -----------------------------------------------------------------------------
# Requirements:
# • bash 4.x or later (uses associative arrays)
# • curl
#
# -----------------------------------------------------------------------------
# Author: Phil Tanner <phil@philtanner.com>
# Created: 2026-08
# Last updated: 2026-08-21
#
# Licence: (MIT licence: https://mit-license.org/)
# Copyright © 2026 Phil Tanner
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the “Software”), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
# -----------------------------------------------------------------------------
# Version: 1.0.0
# -----------------------------------------------------------------------------
#
# -----------------------------------------------------------------------------
# TODO:
# • nothing \o/
#
# -----------------------------------------------------------------------------
set -euo pipefail
# Default allow spaces in filenames/paths without quoting (just in case)
IFS=$'\n\t'
# Standard Nagios plugin return codes.
NAGIOS_STATUS_OK=0
NAGIOS_STATUS_WARNING=1
NAGIOS_STATUS_CRITICAL=2
NAGIOS_STATUS_UNKNOWN=3
# Variables for the outcome
NOTIFICATIONTYPE=""
HOSTNAME=""
STATE=""
ENDPOINT=""
ALERTTYPE="host"
OUTPUT=""
TIMESTAMP=""
SERVICE=""
BODY=""
usage()
{
echo "NAME"
echo " $0"
echo "SYNOPSIS"
echo " $0 "
echo "DESCRIPTION"
echo " This script is a wrapper to send Nagios alerts to a"
echo " ntfy.sh endpoint, designed to be configured for host"
echo " or service level alerts, and have some standardised"
echo " emoji/tags based on the values passed."
echo " The options are as follows:"
echo " --notification-type <type>"
echo " The type of notification. This should be the"
echo " value held by the \$NOTIFICATIONTYPE\$ value."
echo " Valid options:"
echo " https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/macrolist.html#notificationtype"
echo " --host <name>"
echo " The name of the host as the source of the"
echo " notification. Needed for host and service"
echo " notification types."
echo " --state <state>"
echo " The state of the host, or the service. Valid"
echo " options can be either a service state:"
echo " https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/macrolist.html#servicestate"
echo " Or a host state:"
echo " https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/macrolist.html#hoststate"
echo " depending upon the value of [type] argument"
echo " --endpoint <uri>"
echo " The ntfy.sh server and topic you want to send"
echo " notifications to."
echo " --output '<content>'"
echo " The output from the Nagios check. This will be"
echo " in the body of the ntfy message. Usually this"
echo " would expect to be either one of the Nagios macros"
echo " \$HOSTOUTPUT\$ or \$SERVICEOUTPUT\$"
echo " --timestamp '<timestamp>'"
echo " You can use the \$LONGDATETIME\$ macro here"
echo " --service '<name>'"
echo " The name of the service generating the notification"
echo " NOTE: Optional."
echo " If not defined, the script assumes this is a"
echo " notification about a host, NOT about a service."
exit "${1:-$NAGIOS_STATUS_UNKNOWN}"
}
dedupe_csv()
{
local csv="$1"
local item
local -a items=()
local -A seen=()
local -a unique=()
IFS=',' read -ra items <<< "$csv"
for item in "${items[@]}"; do
if [[ -z "${seen[$item]+x}" ]]; then
seen[$item]=1
unique+=("$item")
fi
done
IFS=','
printf '%s' "${unique[*]}"
}
require_arg()
{
if [[ $# -lt 2 || -z "$2" ]]; then
echo "UNKNOWN - Option $1 requires an argument"
exit $NAGIOS_STATUS_UNKNOWN
fi
}
while [[ $# -gt 0 ]]; do
case "$1" in
--notification-type)
require_arg "$@"
NOTIFICATIONTYPE="$2"
shift 2
;;
--host)
require_arg "$@"
HOSTNAME="$2"
shift 2
;;
--state)
require_arg "$@"
STATE="$2"
shift 2
;;
--endpoint)
require_arg "$@"
ENDPOINT="$2"
shift 2
;;
--output)
require_arg "$@"
OUTPUT="$2"
shift 2
;;
--timestamp)
require_arg "$@"
TIMESTAMP="$2"
shift 2
;;
--service)
SERVICE="$2"
ALERTTYPE="service"
shift 2
;;
--help|-h)
usage "$NAGIOS_STATUS_OK"
;;
*)
echo "UNKNOWN - Unknown argument: $1"
usage
;;
esac
done
# Sense-check our variables
if [[ -z "$NOTIFICATIONTYPE" ]]; then
echo "UNKNOWN - Notification type not specified"
exit $NAGIOS_STATUS_UNKNOWN
fi
if [[ -z "$HOSTNAME" ]]; then
echo "UNKNOWN - Host not specified"
exit $NAGIOS_STATUS_UNKNOWN
fi
if [[ -z "$STATE" ]]; then
echo "UNKNOWN - State not specified"
exit $NAGIOS_STATUS_UNKNOWN
fi
if [[ -z "$ENDPOINT" ]]; then
echo "UNKNOWN - Endpoint not specified"
exit $NAGIOS_STATUS_UNKNOWN
fi
if [[ "$ALERTTYPE" == "service" && -z "$SERVICE" ]]; then
echo "UNKNOWN - Service not specified"
exit $NAGIOS_STATUS_UNKNOWN
fi
if [[ "${ALERTTYPE}" = "host" ]]; then
TAGS="nagios,computer"
# Host states: "UP", "DOWN", or "UNREACHABLE".
case $STATE in
UP)
TAGS="${TAGS},white_check_mark"
;;
DOWN)
TAGS="${TAGS},x"
;;
UNREACHABLE)
TAGS="${TAGS},grey_question"
;;
TEST)
TAGS="${TAGS},hammer_and_wrench"
;;
*)
echo "UNKNOWN - state not specified, or unknown value. Check if it is a host state."
echo "https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/macrolist.html#hoststate"
exit $NAGIOS_STATUS_CRITICAL
;;
esac
TITLE="${NOTIFICATIONTYPE} '${HOSTNAME}' ${STATE}"
elif [[ "${ALERTTYPE}" = "service" ]]; then
TAGS="nagios,gear"
# Service states: "OK", "WARNING", "UNKNOWN", or "CRITICAL"
case $STATE in
OK)
TAGS="${TAGS},white_check_mark"
;;
WARNING)
TAGS="${TAGS},warning"
;;
UNKNOWN)
TAGS="${TAGS},grey_question"
;;
CRITICAL)
TAGS="${TAGS},x"
;;
TEST)
TAGS="${TAGS},hammer_and_wrench"
;;
*)
echo "UNKNOWN - state not specified, or unknown value. Check if it is a service state."
echo "https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/macrolist.html#servicestate"
exit $NAGIOS_STATUS_CRITICAL
;;
esac
TITLE="${NOTIFICATIONTYPE} '${HOSTNAME}:${SERVICE}' ${STATE}"
BODY="
**Service:**
${SERVICE}"
fi
# https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/macrolist.html#notificationtype
case $NOTIFICATIONTYPE in
PROBLEM)
STATUS_ICON="arrow_heading_down"
PRIORITY=5
;;
RECOVERY)
STATUS_ICON="arrow_heading_up"
PRIORITY=4
;;
ACKNOWLEDGEMENT)
STATUS_ICON="raising_hand"
PRIORITY=2
;;
FLAPPINGSTART)
STATUS_ICON="arrow_up_down"
PRIORITY=3
;;
FLAPPINGSTOP)
STATUS_ICON="ballot_box_with_check"
PRIORITY=3
;;
FLAPPINGDISABLED)
STATUS_ICON="no_entry_sign"
PRIORITY=2
;;
DOWNTIMESTART)
STATUS_ICON="clock10"
PRIORITY=1
;;
DOWNTIMEEND)
STATUS_ICON="clock2"
PRIORITY=1
;;
DOWNTIMECANCELLED)
STATUS_ICON="clock6"
PRIORITY=1
;;
TEST)
STATUS_ICON="hammer_and_wrench"
PRIORITY=1
;;
*)
echo "UNKNOWN - notification type not specified, or unknown value"
exit $NAGIOS_STATUS_CRITICAL
;;
esac
TAGS="${TAGS},${STATUS_ICON}"
# Remove any doubled up tags
TAGS=$(dedupe_csv "$TAGS")
BODY="**Additional Info:**
${OUTPUT}
**Notification Type:**
${NOTIFICATIONTYPE}
**Hostname:**
${HOSTNAME}
**Status:**
${STATE}${BODY}
**Date:**
${TIMESTAMP}"
# Send our notification out to our ntfy.sh endpoint
CURL_ERROR=$(mktemp)
if HTTP_STATUS=$(
/usr/bin/curl \
--write-out '%{http_code}' \
--output /dev/null \
--silent \
--show-error \
-w '%{http_code}' \
-H "Title: ${TITLE}" \
-H "Priority: ${PRIORITY}" \
-H "X-Tags: ${TAGS}" \
-H "X-Markdown: yes" \
-d "${BODY}" \
"$ENDPOINT" \
2>"$CURL_ERROR"
); then
if [[ "$HTTP_STATUS" =~ ^2[0-9][0-9]$ ]]; then
rm -f "$CURL_ERROR"
exit $NAGIOS_STATUS_OK
else
echo "UNKNOWN - curl failed: $(<"$CURL_ERROR")"
rm -f "$CURL_ERROR"
exit $NAGIOS_STATUS_UNKNOWN
fi
else
echo "UNKNOWN - curl failed to contact ntfy"
exit $NAGIOS_STATUS_UNKNOWN
fi
rm -f "$CURL_ERROR"
# If we've gotten here, we did something wrong since we should've handled everything above
echo 'Script failed, manual intervention required.'
exit $NAGIOS_STATUS_UNKNOWN
In the comments at the top of the bash script, you can see the definitions that need to be added to the Nagios commands.cfg file. I have also stored the URL for my ntfy.sh server as $USER3$ in my resource.cfg file (and $USER2$ points to my custom scripts location for Nagios – which I think is the default).
Now I can receive Nagios alerts direct to my mobile phone, pretty much instantaneously. And find out whether or not the Pentagon has just ordered a bucket load of pizza….