Automating Nagios Configuration with Puppet
At ten servers, maintaining Nagios by hand is annoying but manageable. At fifty, it's a liability. Every time you provision a new host, you're editing hosts.cfg and services.cfg on the Nagios server, reloading the daemon, and hoping you didn't fat-finger the hostname or forget a service check. When a host disappears, you need to remember to clean up its definitions or Nagios starts alerting on a machine that no longer exists.
Puppet solves this elegantly with a pattern called exported resources. It's one of the more advanced Puppet concepts, and it has real prerequisites, but when it clicks it feels like genuine infrastructure automation — not just config file distribution.
The Core Idea: Exported Resources
Normally a Puppet resource only applies to the node where it's declared. Exported resources break that boundary. A node can export a resource into PuppetDB, and another node can collect it and apply it locally.
This is the entire conceptual model for Nagios automation: each node exports its own monitoring definitions, and the Nagios server collects them all. Add a new node to your Puppet environment and it shows up in Nagios automatically on the next puppet run. Decommission it and the exported resource disappears from PuppetDB, and eventually from your Nagios configs.
The syntax distinction is small but important:
@@resource { ... }— exports a resource (the@@is the export sigil)Resource <<| |>>— collects all exported resources of that type
PuppetDB is the glue. Without it, exported resources don't work. PuppetDB stores every exported resource so the Nagios server can query for them. If you're not already running PuppetDB, that's the first thing to set up. It's not optional here.
The Node Manifest
Puppet ships with built-in resource types for Nagios: nagios_host, nagios_service, nagios_command, nagios_hostgroup, and others. These types write files into /etc/nagios/conf.d/ (or wherever your distro puts auto-included configs). Here's what a web server node's Puppet manifest looks like:
# Applied to each web server node
@@nagios_host { $::fqdn:
ensure => present,
address => $::ipaddress,
alias => $::hostname,
hostgroups => 'webservers,linux-servers',
use => 'linux-server',
target => '/etc/nagios/conf.d/puppet-hosts.cfg',
}
@@nagios_service { "http-${::fqdn}":
ensure => present,
host_name => $::fqdn,
service_description => 'HTTP',
check_command => 'check_http',
use => 'generic-service',
target => '/etc/nagios/conf.d/puppet-services.cfg',
}
@@nagios_service { "ssh-${::fqdn}":
ensure => present,
host_name => $::fqdn,
service_description => 'SSH',
check_command => 'check_ssh',
use => 'generic-service',
target => '/etc/nagios/conf.d/puppet-services.cfg',
}
@@nagios_service { "disk-${::fqdn}":
ensure => present,
host_name => $::fqdn,
service_description => 'Disk Usage',
check_command => 'check_nrpe!check_disk',
use => 'generic-service',
target => '/etc/nagios/conf.d/puppet-services.cfg',
}
@@nagios_service { "memory-${::fqdn}":
ensure => present,
host_name => $::fqdn,
service_description => 'Memory Usage',
check_command => 'check_nrpe!check_mem',
use => 'generic-service',
target => '/etc/nagios/conf.d/puppet-services.cfg',
}
The target parameter tells Puppet which file to write the definition into. Keep hosts and services in separate files — it makes debugging easier when you need to inspect what Puppet generated.
The Nagios Server Manifest
The Nagios server collects everything and manages the daemon:
# Applied to the Nagios server node
class nagios_server {
package { ['nagios3', 'nagios-nrpe-plugin']:
ensure => installed,
}
# Collect all exported Nagios host definitions
Nagios_host <<| |>> {
notify => Service['nagios3'],
}
# Collect all exported Nagios service definitions
Nagios_service <<| |>> {
notify => Service['nagios3'],
}
service { 'nagios3':
ensure => running,
enable => true,
}
# Include the auto-generated config directory
file { '/etc/nagios/conf.d':
ensure => directory,
owner => 'nagios',
group => 'nagios',
recurse => true,
purge => true, # removes stale definitions for decommissioned hosts
}
}
The purge => true on the conf.d directory is important. Without it, when you decommission a host and its exported resources disappear from PuppetDB, the old config file entries stick around and Nagios keeps alerting. With purge, Puppet removes files it no longer manages.
The File Permissions Gotcha
Puppet runs as root, but Nagios reads its config as the nagios user. The files Puppet generates in /etc/nagios/conf.d/ need to be readable by nagios. By default Puppet writes them as mode 0600 owned by root, which breaks things silently — Nagios just can't read them and ignores the directory.
Fix this by setting the file mode in your resource defaults or in the target file declarations:
File {
owner => 'nagios',
group => 'nagios',
mode => '0644',
}
Put this in your nagios_server class scope and it applies to everything Puppet manages there.
Verify Before Trusting
After a puppet run on the Nagios server, verify that resources were actually collected:
puppet resource nagios_host
This queries the local Puppet resource catalog and shows you all the nagios_host entries Puppet knows about. If a node you expected is missing, check PuppetDB — the most common cause is a failed puppet run on the client node, so the export never happened.
Before reloading Nagios, always validate the config:
nagios3 -v /etc/nagios3/nagios.cfg
If there are syntax errors in the generated files, this will tell you. The Puppet notify => Service['nagios3'] trigger will attempt a reload whenever configs change — if validation fails, Nagios doesn't reload, but your service won't bounce either. I add an exec resource that runs the validation check and gates the reload, though for most setups letting Nagios restart and checking its status is sufficient.
Honest Assessment
Exported resources are not a beginner Puppet pattern. You need PuppetDB running and healthy, you need your nodes to be actively running puppet agent (not just puppet apply), and the first time something goes wrong it's not obvious whether the problem is in the export, the collection, the file permissions, or the Nagios config syntax. Budget time for troubleshooting the first time you set this up.
The payoff is real, though. Once it's working, you genuinely never touch the Nagios server's config files again. A new web server shows up in monitoring on the next puppet run. A decommissioned host disappears. The configuration is authoritative from the nodes themselves, which is exactly where the knowledge about what a node does should live.
For shops with a mature Puppet environment and PuppetDB already in place, this is the right pattern.
Updated March 2026: Ansible has largely replaced Puppet for Nagios automation in most shops I've seen since 2017 or so. The agentless model and simpler mental overhead won out, even if you lose the automatic self-registration behavior. More significantly, Nagios itself has been replaced by Prometheus and Grafana in most modern environments — the pull-based metrics model with alerting rules in code fits the containerized world better than Nagios's host/service check model. I wrote a follow-up post in October 2016 covering the Ansible approach to Nagios config management if you're on that path.