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.
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.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
| State | Exit code | Meaning |
|---|---|---|
| OK | 0 | Check passed, value within threshold |
| WARNING | 1 | Value crossed the warning threshold — worth watching |
| CRITICAL | 2 | Value crossed the critical threshold — needs attention now |
| UNKNOWN | 3 | Plugin 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 anexit 2 is just as valid a plugin as a compiled C binary.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.Installation
Install Nagios Core and the standard plugins package on the monitoring server itself. Package names and paths differ slightly between distribution families.
# 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/pluginsVerify 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 runningnagios -v /etc/nagios/nagios.cfg (path varies by distro) before every systemctl restart — see the Commands section.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.
# Debian/Ubuntu
sudo apt install -y nagios-nrpe-server nagios-plugins-contrib
# RHEL/Rocky (EPEL)
sudo dnf install -y epel-release nrpe nagios-plugins-allEdit /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:
# 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 400sudo 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 # RHELDefine 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:
define command {
command_name check_nrpe
command_line /usr/lib/nagios/plugins/check_nrpe -H $HOSTADDRESS$ -c $ARG1$
}Define hosts
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
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":
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:
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
}Commands
Validate configuration before every restart
# Debian/Ubuntu path
sudo nagios4 -v /etc/nagios4/nagios.cfg
# RHEL/Rocky path
sudo nagios -v /etc/nagios/nagios.cfgService management
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 historyTesting 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:
# 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;20000check_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.Examples
A complete monitoring setup for one host, built up service by service:
Define the host
Add adefine hostblock forweb01inhosts.cfgwith its IP and the built-inlinux-servertemplate, which already wires upcheck_pingas the host check.Add a disk usage check
Install NRPE onweb01, definecommand[check_disk]in itsnrpe.cfg, then add acheck_nrpe!check_diskservice on the Nagios server.Add a CPU/load check
Same pattern withcommand[check_load]— warn at load 5/4/3 and go critical at 10/8/6 across the 1/5/15-minute averages.Add an HTTP check
This one runs directly from the Nagios server, no NRPE needed:check_command check_httpwithcheck_http -H web01.internal -u /healthz -e 200as the underlying plugin call, verifying both reachability and a healthy response code.Verify and reload
Runnagios -v nagios.cfgto catch typos, thensystemctl reload nagiosand confirm all three services appear as OK on the web UI within one check interval.
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
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.
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 withhtpasswd.Roll out NRPE via config management
Push the samenrpe.cfg(withallowed_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.Define hosts and services in bulk
Loop a host template and a standard service set (check_disk,check_load,check_procsfor 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).Wire up email via Postfix
Configure Postfix as a satellite relay on the Nagios server (relayhostpointed at the company's SMTP provider), confirmmail -s "test" jsmith@example.comdelivers, then letnotify-service-by-emailuse it as the local MTA.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 acommandobject, and add it toservice_notification_commandsalongside the email command.Tune thresholds and retries to kill noise
Setmax_check_attempts 3with a one-minuteretry_check_intervalso 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.
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'dhost_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 thecommand[]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") oncheck_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
- Manage
nrpe.cfgand host/service definitions through Ansible or another config tool — hand-editing eight hosts individually guarantees drift. - Set
max_check_attemptsandretry_check_intervaldeliberately 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 Hardening
- Restrict allowed_hosts — set NRPE's
allowed_hoststo 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/nrpeservice accounts should have only the specificsudorules 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
Cheat Sheet
# --- 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> 5666Summary
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
- 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