Skip to content

Nagios Monitoring: NRPE, Checks & Alerting

Get real visibility into servers and services with Nagios Core, NRPE checks, and alerting that actually reaches the right people.

Nagios CoreNRPEPostfix
RR

Ranjith R

Linux SysAdmin & Cloud Engineer

Published March 15, 2026Updated April 1, 2026
9 min readIntermediate#Nagios#Monitoring#NRPE#Alerting
Share
Section 01

Introduction

Monitoring only earns its keep if it tells you a service is down before a customer does, and tells the right person, not a channel everyone has muted. Nagios Core has been the reference implementation of that idea since 2002: a lightweight scheduler that runs small, single-purpose plugins against hosts and services, tracks their state, and fires notifications when that state changes. It is not the newest tool in this space, but it is the one you will find running quietly in the largest number of production data centers — which makes it a skill worth having even if your next job runs Prometheus.

This guide covers Nagios the way you would actually deploy it: a central Nagios server, the NRPE agent on every monitored host so checks that need local visibility (disk, load, processes) can run where the data actually is, and alerting that reaches a human over email via Postfix — with a note on wiring the same alert into Slack. By the end you will be able to stand up monitoring for a small fleet of Linux servers from scratch and explain every line of the resulting config.

Who this is for

  • Linux administrators who need server/service visibility without a heavyweight metrics stack.
  • Engineers supporting a fleet of VMs where "is it up" matters more than time-series dashboards.
  • Anyone prepping for interviews where classic monitoring concepts (active/passive checks, NRPE, escalations) come up.
Section 02

Prerequisites

Have the following ready before working through the installation and configuration sections:

  • One Linux host to act as the Nagios server (Ubuntu/Debian or RHEL/Rocky — this guide covers both).
  • One or more Linux hosts to monitor, with root or sudo access on each.
  • Root/sudo access on the Nagios server for package installs and service management.
  • Working outbound SMTP — either a local Postfix install or relay credentials — for email alerts.
  • Basic familiarity with systemd service management and editing plain-text config files.
  • Firewall access to open TCP port 5666 (NRPE) between the Nagios server and monitored hosts.

Nagios Core vs. Nagios XI

Everything here is the free, open-source Nagios Core. Nagios XI is a commercial product built on top of it with a web-based config UI — the underlying concepts (hosts, services, commands, contacts) are identical.
Section 03

Theory

Active vs. passive checks

In an active check, Nagios initiates the check on its own schedule — it runs a plugin (locally, or remotely via NRPE) and waits for the result. This is the default and covers the vast majority of checks: disk space, load, HTTP status, ping.

In a passive check, the monitored host (or an external process) pushes a result intoNagios instead of Nagios asking for it — usually via the external command file or NSCA. This suits checks Nagios can't easily schedule itself: results from a batch job, a security scanner, or a check that only makes sense to run from inside an application's own deploy pipeline.

Check intervals and state changes

Every service definition has a normal_check_interval (how often to check when the state is stable) and a retry_check_interval(a faster interval used while a problem is unconfirmed). A check that returns non-OK doesn't alert immediately — it enters a SOFT state and Nagios re-checks it max_check_attempts times at the retry interval. Only after it fails that many times in a row does it become a HARD state and trigger a notification. This exists specifically to absorb one-off blips (a slow DNS lookup, a momentary CPU spike) without paging anyone.

The four states

StateExit codeMeaning
OK0Check passed, value within threshold
WARNING1Value crossed the warning threshold — worth watching
CRITICAL2Value crossed the critical threshold — needs attention now
UNKNOWN3Plugin couldn't determine a state — bad args, timeout, missing dependency, crash

Plugin exit codes are the entire contract

A Nagios plugin is any executable that accepts the standard arguments, prints a one-line status message to stdout, and exits with one of the four codes above. That contract is why the plugin ecosystem is so large — a two-line bash script with an exit 2 is just as valid a plugin as a compiled C binary.
Section 04

Architecture

A typical deployment has one Nagios server polling many monitored hosts. Checks that only need external visibility (ping, HTTP, TCP port) run directly from the server. Checks that need to see inside the host — disk usage, load average, process counts — run through the NRPE (Nagios Remote Plugin Executor) agent installed on each monitored host, which executes a local plugin and returns just the result over an encrypted connection on TCP port 5666.

Nagios server polls local plugins directly and remote hosts via NRPE, then routes state changes to email and Slack

NRPE never initiates anything

The NRPE daemon on a monitored host only ever answers requests from the Nagios server — it cannot push data or open a connection back. This is why passive checks (when needed) go through a separate mechanism like NSCA rather than NRPE.
Section 05

Installation

Install Nagios Core and the standard plugins package on the monitoring server itself. Package names and paths differ slightly between distribution families.

terminal
# Update package index
sudo apt update

# Nagios Core, the standard plugin bundle, and Apache for the web UI
sudo apt install -y nagios4 nagios-plugins-contrib apache2

# Set the web UI admin password (user: nagiosadmin)
sudo htpasswd -c /etc/nagios4/htpasswd.users nagiosadmin

# Enable and start
sudo systemctl enable --now nagios4
sudo systemctl enable --now apache2

# Config lives under /etc/nagios4, plugins under /usr/lib/nagios/plugins

Verify config before every restart

Nagios will refuse to start on a broken config, but it fails quietly in the logs. Get in the habit of running nagios -v /etc/nagios/nagios.cfg (path varies by distro) before every systemctl restart — see the Commands section.
Section 06

Configuration

Install and configure NRPE on each monitored host

NRPE is installed on the monitored host, not the Nagios server. It runs local plugins on request and needs an explicit allow-list of which IPs may query it.

terminal (on the monitored host)
# Debian/Ubuntu
sudo apt install -y nagios-nrpe-server nagios-plugins-contrib

# RHEL/Rocky (EPEL)
sudo dnf install -y epel-release nrpe nagios-plugins-all

Edit /etc/nagios/nrpe.cfg (Debian: /etc/nagios/nrpe.cfg, RHEL: same path) to allow the Nagios server's IP and define the commands it's allowed to run:

/etc/nagios/nrpe.cfg
# Only accept connections from the Nagios server
allowed_hosts=127.0.0.1,10.0.0.10

# Bind address and port (default is fine for most setups)
server_port=5666

# Allow arguments passed from the server in command definitions
dont_blame_nrpe=1

# Command definitions this host will answer to
command[check_disk]=/usr/lib/nagios/plugins/check_disk -w 20% -c 10% -p /
command[check_load]=/usr/lib/nagios/plugins/check_load -w 5,4,3 -c 10,8,6
command[check_users]=/usr/lib/nagios/plugins/check_users -w 5 -c 10
command[check_zombie_procs]=/usr/lib/nagios/plugins/check_procs -w 5 -c 10 -s Z
command[check_total_procs]=/usr/lib/nagios/plugins/check_procs -w 250 -c 400
terminal (on the monitored host)
sudo systemctl enable --now nagios-nrpe-server   # Debian/Ubuntu
sudo systemctl enable --now nrpe                 # RHEL/Rocky

# Open the NRPE port to the Nagios server only
sudo ufw allow from 10.0.0.10 to any port 5666 proto tcp     # Ubuntu
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.10" port protocol="tcp" port="5666" accept'
sudo firewall-cmd --reload   # RHEL

Define the NRPE command on the Nagios server

On the Nagios server, one reusable command object wraps the check_nrpe plugin so it can be referenced by host name and remote command name from any service definition:

/etc/nagios/objects/commands.cfg
define command {
    command_name    check_nrpe
    command_line    /usr/lib/nagios/plugins/check_nrpe -H $HOSTADDRESS$ -c $ARG1$
}

Define hosts

/etc/nagios/objects/hosts.cfg
define host {
    use             linux-server
    host_name       web01
    alias           Web Server 01
    address         10.0.0.21
    max_check_attempts   3
    check_period    24x7
    notification_interval  30
    notification_period    24x7
}

define host {
    use             linux-server
    host_name       db01
    alias           Primary Database
    address         10.0.0.22
    parents         web01
    max_check_attempts   3
    check_period    24x7
    notification_period    24x7
}

Parent/child host relationships

parents web01 tells Nagios that db01 is only reachable through web01. If web01 goes down, Nagios marks db01 as UNREACHABLE instead of DOWN and suppresses redundant notifications for it — this is what stops one switch failure from paging you fifty times.

Define services

/etc/nagios/objects/services.cfg
define service {
    use                     generic-service
    host_name               web01
    service_description     Disk Usage /
    check_command            check_nrpe!check_disk
    normal_check_interval    5
    retry_check_interval     1
    max_check_attempts       3
}

define service {
    use                     generic-service
    host_name               web01
    service_description     HTTP
    check_command            check_http
    normal_check_interval    2
    retry_check_interval     1
}

define service {
    use                     generic-service
    host_name               web01
    service_description     Current Load
    check_command            check_nrpe!check_load
    normal_check_interval    5
}

Define contacts, contact groups, and notification periods

Contacts control whogets notified, through which command, during which hours — this is the piece that separates "monitoring exists" from "monitoring actually reaches someone":

/etc/nagios/objects/contacts.cfg
define contact {
    contact_name                    jsmith
    alias                           J Smith - On Call
    service_notification_period     24x7
    host_notification_period        24x7
    service_notification_options    w,u,c,r
    host_notification_options       d,u,r
    service_notification_commands   notify-service-by-email
    host_notification_commands      notify-host-by-email
    email                           jsmith@example.com
}

define contactgroup {
    contactgroup_name    ops-team
    alias                 Operations Team
    members               jsmith
}

Reference the contact group from each host/service (or set it once as a default in generic-service) so notifications actually fire:

/etc/nagios/objects/templates.cfg
define service {
    name                            generic-service
    active_checks_enabled           1
    passive_checks_enabled          1
    check_period                    24x7
    max_check_attempts              3
    contact_groups                  ops-team
    notification_interval           60
    notification_period             24x7
    register                        0
}
Section 07

Commands

Validate configuration before every restart

terminal
# Debian/Ubuntu path
sudo nagios4 -v /etc/nagios4/nagios.cfg

# RHEL/Rocky path
sudo nagios -v /etc/nagios/nagios.cfg

Service management

terminal
sudo systemctl restart nagios4     # Debian/Ubuntu
sudo systemctl restart nagios      # RHEL/Rocky
sudo systemctl status nagios
sudo systemctl reload nagios       # re-read config without dropping check history

Testing NRPE manually from the Nagios server

Before trusting a service definition, always confirm the plugin works end to end by calling it exactly the way Nagios will:

terminal (on the Nagios server)
# Confirm NRPE is reachable and check its version
/usr/lib/nagios/plugins/check_nrpe -H 10.0.0.21

# Run a specific remote command exactly as the service definition will
/usr/lib/nagios/plugins/check_nrpe -H 10.0.0.21 -c check_disk

# Expected output:
# DISK OK - free space: / 12000 MB (65% inode=88%);| /=6400MB;16000;18000;0;20000

check_nrpe vs check_nrpe -2

If you see a socket timeout with no version string returned, add -2 to force NRPE protocol v2 packets — some older/hardened NRPE builds reject v3 by default.
Section 08

Examples

A complete monitoring setup for one host, built up service by service:

  1. Define the host

    Add a define host block for web01 in hosts.cfg with its IP and the built-in linux-server template, which already wires up check_ping as the host check.
  2. Add a disk usage check

    Install NRPE on web01, define command[check_disk] in its nrpe.cfg, then add a check_nrpe!check_disk service on the Nagios server.
  3. Add a CPU/load check

    Same pattern with command[check_load] — warn at load 5/4/3 and go critical at 10/8/6 across the 1/5/15-minute averages.
  4. Add an HTTP check

    This one runs directly from the Nagios server, no NRPE needed: check_command check_http with check_http -H web01.internal -u /healthz -e 200 as the underlying plugin call, verifying both reachability and a healthy response code.
  5. Verify and reload

    Run nagios -v nagios.cfg to catch typos, then systemctl reload nagios and confirm all three services appear as OK on the web UI within one check interval.
Section 09

Screenshots

Nagios web UI — host status overview showing all monitored hosts in UP/DOWN state with a summary of service health per host

Screenshot placeholder

Nagios web UI — service detail view for a single host, showing check_disk history, current state, and last check output

Screenshot placeholder

Section 10

Real World Example

Scenario:a team runs eight Linux servers behind a load balancer with no monitoring at all. The requirement: alert on disk usage before it becomes an outage, alert immediately if a service process dies, and get both into email and the team's Slack channel — without paging anyone at 3 AM for a blip that self-resolves in one retry.

  1. Stand up the Nagios server

    Install Nagios Core + plugins on a dedicated small VM, put it behind the internal network only, and set an admin password for the web UI with htpasswd.
  2. Roll out NRPE via config management

    Push the same nrpe.cfg (with allowed_hostsset to the Nagios server's IP) and package install to all eight hosts through Ansible, so every host answers the same set of commands — no per-host drift.
  3. Define hosts and services in bulk

    Loop a host template and a standard service set (check_disk, check_load, check_procs for the app process) across all eight hosts, tuning thresholds only where a host is genuinely different (e.g. the database box gets a tighter load threshold).
  4. Wire up email via Postfix

    Configure Postfix as a satellite relay on the Nagios server (relayhost pointed at the company's SMTP provider), confirm mail -s "test" jsmith@example.com delivers, then let notify-service-by-email use it as the local MTA.
  5. Add a Slack notification command

    Write a small script that POSTs the standard Nagios macros ($HOSTNAME$, $SERVICESTATE$, $SERVICEOUTPUT$) to a Slack incoming webhook, define it as a command object, and add it to service_notification_commands alongside the email command.
  6. Tune thresholds and retries to kill noise

    Set max_check_attempts 3 with a one-minute retry_check_interval so a check has to fail three times in three minutes before it pages — enough to survive a momentary spike but still catch a real outage fast.
Troubleshooting

Common Issues

CHECK_NRPE: Error - Could not complete SSL handshake / Connection refused

Either the NRPE daemon isn't running on the target host (systemctl status nrpe), or its allowed_hosts in nrpe.cfgdoesn't include the Nagios server's IP. Restart NRPE after any nrpe.cfg change — it does not hot-reload.

nagios -v reports errors and won't let the service restart

Read the very first error line, not the last — one bad reference (a typo'd host_name or missing command_name) cascades into dozens of downstream "could not resolve" errors that all disappear once the first one is fixed.

Service checks stuck in UNKNOWN

UNKNOWN almost always means the plugin itself failed to run — wrong path in the command[]definition, missing execute permission, or a plugin argument that doesn't exist in the installed plugin version. Run the exact command[] line by hand as the nagios/nrpe user to reproduce it outside of Nagios.

Checks are fine but notifications never arrive

Check three things in order: notifications_enabled=1 in nagios.cfg, the contact's notification_period actually covers the current time, and notification_options includes the state that just occurred (a contact configured only for c,r will never notify on warning).

Firewall blocking NRPE port 5666

A silent hang (not an immediate "connection refused") on check_nrpe -H <host> almost always means a firewall — either ufw/firewalld on the monitored host or a security group/ACL upstream — is dropping the packet rather than rejecting it. Confirm with nc -zv <host> 5666 from the Nagios server.
Best Practices

Best Practices

  • Manage nrpe.cfg and host/service definitions through Ansible or another config tool — hand-editing eight hosts individually guarantees drift.
  • Set max_check_attempts and retry_check_interval deliberately per service — a flaky HTTP check needs more retries than a database process check.
  • Use host templates (generic-host, linux-server) and service templates instead of repeating the same attributes on every definition.
  • Set up parent/child host relationships so a switch or router failure doesn't generate a storm of unrelated DOWN alerts.
  • Route different severities to different channels — CRITICAL to a paging service, WARNING to a low-priority Slack channel — instead of one firehose.
  • Keep a staging Nagios instance (or a spare install) to test config changes before touching production monitoring — a broken monitoring config is itself an outage.
Security

Security Hardening

  • Restrict allowed_hosts — set NRPE's allowed_hosts to the Nagios server's exact IP, never a wildcard or broad subnet.
  • Firewall the NRPE port — allow TCP 5666 only from the Nagios server's IP, both on the host firewall and any upstream security group.
  • Never run checks as root — the nagios/nrpe service accounts should have only the specific sudo rules a check plugin genuinely needs (e.g. reading a restricted log), not blanket root.
  • Secure the web UI — put it behind HTTP basic auth at minimum (already required for the CGIs), and terminate it behind TLS (a reverse proxy with Let's Encrypt is the common pattern) rather than serving it over plain HTTP.
  • Keep dont_blame_nrpe off unless required — enabling command-line arguments from the server side widens what a compromised Nagios server could execute on every monitored host; scope it tightly if you must use it.
  • Patch the NRPE package — it has had real CVEs historically; track updates for it the same way you would any network-facing daemon.
Interview Questions

Interview Questions

An active check is initiated by Nagios itself on a schedule — it runs a plugin and waits for the result. A passive check is submitted to Nagios from an external source (a cron job, an application, NSCA) through the external command file, for situations where Nagios can't sensibly poll on its own schedule.
Cheat Sheet

Cheat Sheet

nagios-cheatsheet.sh
# --- Validate & control the Nagios server ---
nagios -v /etc/nagios/nagios.cfg          # validate config before restarting
systemctl restart nagios                  # RHEL/Rocky
systemctl restart nagios4                 # Debian/Ubuntu
systemctl reload nagios                   # re-read config, keep check history

# --- NRPE (on monitored host) ---
systemctl restart nrpe                    # RHEL/Rocky (nagios-nrpe-server on Debian)
systemctl status nrpe

# --- Test NRPE from the Nagios server ---
/usr/lib/nagios/plugins/check_nrpe -H <host>                  # confirm reachable + version
/usr/lib/nagios/plugins/check_nrpe -H <host> -c check_disk     # run a specific remote command
/usr/lib/nagios/plugins/check_nrpe -H <host> -2 -c check_load  # force NRPE protocol v2

# --- Run a plugin directly, exactly like Nagios would ---
/usr/lib/nagios/plugins/check_disk -w 20% -c 10% -p /
/usr/lib/nagios/plugins/check_http -H example.com -u /healthz -e 200
/usr/lib/nagios/plugins/check_load -w 5,4,3 -c 10,8,6

# --- Minimal command/host/service skeleton ---
# commands.cfg
define command {
    command_name  check_nrpe
    command_line  /usr/lib/nagios/plugins/check_nrpe -H $HOSTADDRESS$ -c $ARG1$
}

# hosts.cfg
define host {
    use          linux-server
    host_name    web01
    address      10.0.0.21
}

# services.cfg
define service {
    use                   generic-service
    host_name             web01
    service_description   Disk Usage /
    check_command         check_nrpe!check_disk
}

# --- Check firewall reachability to NRPE port ---
nc -zv <host> 5666
Summary

Summary

Nagios earns its long production track record through a small number of composable ideas: plugins that speak a strict exit-code contract, active checks scheduled by the server and passive checks pushed in when that doesn't fit, NRPE for visibility inside remote hosts, and a notification pipeline (contacts, contact groups, notification periods) that decides exactly who hears about a problem and when. Get those fundamentals — plus the SOFT/HARD state machine that keeps you from paging on every blip — solid on a small fleet, and scaling to hundreds of hosts is mostly more of the same config, driven through Ansible instead of by hand.

Resources

Resources

  • Official Nagios Core documentation — nagios.org
  • Nagios Exchange (community plugins and configs) — exchange.nagios.org
  • Nagios Plugins project (check_disk, check_http, etc. source and docs) — nagios-plugins.org
  • NRPE project documentation — github.com/NagiosEnterprises/nrpe
  • Postfix documentation (for wiring up email notifications) — postfix.org