Jump to content

Tetra's Nagios Server Upgrade and Implemenation Process and Runbook

From TetraWiki


This is a reproducible, step-by-step runbook for the migration of nagios.tetrain.com from CentOS 6 / hand-built Nagios Core 4.1.1 to Rocky Linux 9 with a current Nagios Core, Thruk, PNP4Nagios, NagiosQL and Livestatus stack, plus a full data migration of the existing monitoring configuration (50 hosts / 711 services). It is the companion to the project's internal decision log (nagios.tetrain.com_upgrade_plan.md), which records why each choice was made, alternatives considered, and open questions; use this page to actually reproduce the work.

Provenance note on exactness: every command and finding below was captured live during this migration session (direct SSH access to both the old production box and the new build box, direct Ansible playbook runs, direct NagiosQL/Thruk/browser interaction). Nothing here is reconstructed after the fact — this is a running log of commands as they were executed, in order, including the real bugs hit and how each was actually fixed.

Status as of this writing: the new box is fully built, migrated, validated, and parked at a temporary IP. The production cutover (Linode IP Transfer) has not happened yet — see "Known-incomplete items" at the end.


Phase 1 — Old server discovery (confirmed via SSH, read-only)

Old box: nagios.tetrain.com172.105.47.191, a Linode VM (li2028-191.members.linode.com), IPA-enrolled, plan g6-nanode-1 (1 vCPU / 1GB RAM / 25GB disk), region ap-west (Mumbai).


cat /etc/redhat-release          # CentOS release 6.8 (Final) -- EOL Nov 2020, never patched to 6.10
uname -r                         # 7.0.5-x86_64-linode173 (Linode-managed kernel, decoupled from userland)
free -m                          # 939M total, 74M free, 228M/511M swap in use -- tight
df -h /                          # 25G total, 7.9G used, 35% used
/usr/local/nagios/bin/nagios -v /usr/local/nagios/etc/nagios.cfg   # hand-built Nagios Core 4.1.1 (2015)
httpd -v                         # Apache/2.2.15 (CentOS), matches external fingerprint
rpm -qa | grep -iE 'ipa-client|sssd-common'   # ipa-client-3.0.0-51, sssd-common-1.13.3-60 -- IPA-enrolled

Authoritative monitoring scope (corrected later in Phase 7 via Nagios's own parser, not a manual grep): 50 hosts, 711 services spanning many separate client production domains (mail servers, ERPs, relays, VPS instances) — this box is not an internal-only tool, its downtime is client-facing.

Access note: no SSH key existed for this box at the start. The root password was provided once directly in chat for this read-only pass, written to a private session-only scratchpad file, used once, then shred -u'd immediately — never persisted to the project folder or repeated back in any document. An SSH key was generated for all subsequent access to the new box (Phase 3); the old box's password should be rotated once handoff is complete.

Verify: nagios -v exits 0 with the counts above; uptime/last reboot confirm last reboot 2026-07-20.


Phase 2 — Target architecture decision

No supported leapp/ELevate in-place path exists from CentOS 6 (unlike the CentOS-7-based tornedo.tetrain.com project) — CentOS 6 predates that tooling generation entirely. Decision: fresh-install migration on a new Rocky 9 VM, not an in-place upgrade.

Critical constraint: the production IP 172.105.47.191 cannot change — it is hard-allow-listed in many client-side firewalls and NRPE allowed_hosts entries. A DNS-only cutover would not fix that. Since this is a Linode VM, the fix is Linode's IP Transfer feature — it moves an IPv4 address between two Linodes in the same data center; nothing downstream needs to know or change.


New Rocky 9 VM, same Linode region, temp IP --configure Nagios + Thruk--> import 50 hosts / 711 services
        |                                                                       |
        +----------------------- run in parallel, validate parity -------------+
                                          |
                     Linode IP Transfer: 172.105.47.191 moves to the new box
                                          |
                 old box kept built + powered off (not deleted) -- instant rollback
                       is "transfer the IP back" if anything's wrong post-swap

Decisions confirmed with the client: target OS Rocky Linux 9, dashboard/UI Thruk (evaluated against standing up a modern Checkmk instead — declined, out of scope, see Phase 12), VM sizing bumped from an exact 1GB match to a 2GB plan since the old box's RAM was already nearly exhausted before adding Thruk on top.


Phase 3 — New build box provisioned (Linode API)


# via Linode API, token from the office infrastructure vault
# confirmed the running production box's exact specs first, then created its replacement:

Old (production) New (build)
Label nagios.tetrain.com nagios-rocky9-build
Plan g6-nanode-1 (1 vCPU, 1GB RAM, 25GB disk) g6-standard-1 (1 vCPU, 2GB RAM, 50GB disk)
Region ap-west (Mumbai) ap-west (Mumbai) -- same region, required for IP Transfer
Image linode/centos6.8 linode/rocky9 (booted as Rocky Linux 9.8 "Blue Onyx")
IP (temporary) 172.105.47.191 (unchanged, stays live) 172.105.40.44
Access root password dedicated ED25519 key only, generated for this box
Hostname li2028-191.members.linode.com / nagios.tetrain.com nagios-rocky9-build.tetrain.com

A root password was also set as a Lish-console fallback only, saved to a local keys/ file marked for rotation, never shown in chat.

Verify: SSH with the new key confirms 2GB RAM (1.6GB free right after boot vs 74MB free on the old box) and 49GB disk free. The production box was not touched — it kept running CentOS 6 / Nagios Core 4.1.1 without interruption for the entire build.


Phase 4 — Stack deploy via Ansible

Ran an adapted Ansible playbook (roles: common, apache, nagios_core, plugins, database, nrpe_client, ncpa_client, pnp4nagios, nagiosql, livestatus, thruk, backup) against nagios-rocky9-build. Clean run: ok=116 changed=7 failed=0.

Component Result
Nagios Core 4.5.13 active, config validates clean
Apache 2.4.62 + TLS (self-signed) active
MariaDB active
Livestatus (compiled, Nagios-4.5-compatible fork) loaded, socket open
PNP4Nagios (current, not the old 0.6.x) installed, Apache config live
NagiosQL 3.5.0 installed, install wizard pending (manual, browser-driven step)
Thruk 3.28 installed, correctly auth-gated

Five real playbook bugs found and fixed (all folded into the durable playbook copy):

  1. TLS download failures: get_url (Python ssl module) unreliably threw [ASN1: NOT_ENOUGH_DATA] against assets.nagios.com, GitHub releases, and download.thruk.org, while curl worked every time from the same box. Fixed by shelling out to curl for every download task.
  2. PNP4Nagios configure flags wrong: --with-httpd-conf isn't a real flag (copied by mistake from the Nagios Core role). Fixed:

./configure --with-http_server=apache --with-http_server_base=/etc/httpd \
  --with-http_confdir=/etc/httpd/conf.d --disable-selinux

  1. Thruk missing RPM dependency: thruk-plugin-reporting is an undeclared dependency of the main thruk package — depsolve failed without it; added to the install list.
  2. Nagios itself never actually started: the role only starts Nagios via a handler notified by the systemd-override file task — a no-op on any re-run where that file is already correct. Added an unconditional enable+start task, matching how Apache/MariaDB/firewalld were already handled.
  3. Thruk dual-auth bypass: the Thruk RPM ships two active auth mechanisms at once — the intended AuthType Basic in thruk.conf, and a cookie/rewrite-based auth in thruk_cookie_auth_vhost.conf granting Require all granted for nearly all of /thruk/. Apache's Require-merging let the blanket grant win, so anonymous requests reached the app unauthenticated. Fixed by disabling the cookie-auth vhost include and populating /etc/thruk/htpasswd with the same admin credentials as Nagios Core.

Verify: all four web UIs (/nagios/, /pnp4nagios/, /nagiosql/, /thruk/) return 401 with no credentials and 200 with the generated admin login.


Phase 5 — NagiosQL wizard + post-deploy fixes

The NagiosQL install wizard is a manual, browser-driven, one-time step by design (not automated by the playbook) — completed at https://<build-ip>/nagiosql/install/.

Three more real bugs surfaced once the box was actually used, all fixed and redeployed clean (ok=119 failed=0):

  1. firewalld runtime/permanent drift: http/https were added to firewalld's permanent config, but the running box's runtime ruleset had gone stale (only pre-existing ssh was actually enforced) — HTTPS was blocked while SSH kept working. Fixed with a full restart, not a reload:

systemctl restart firewalld   # --reload alone was confirmed NOT reliable here

  1. NagiosQL install-wizard requirements missing: php-pear was never installed (added to the Apache role's package list); date.timezone was never set in php.ini (set to Asia/Kolkata). Also learned that php-fpm caches php.ini in memory — restarting only httpd left stale values in effect; added a proper Restart php-fpm handler.
  2. PNP4Nagios graphs structurally broken: two stacked gaps — (a) npcd's systemd unit installs but was never enabled/started (same missing-enablement pattern as bug 4 in Phase 4); (b) the role never set the perfdata file templates in nagios.cfg. Without them Nagios writes perfdata in its own raw positional format, but PNP4Nagios's bulk-mode parser expects its own tagged DATATYPE::.../HOSTNAME::... format (per PNP4Nagios's own shipped nagios-bulk.cfg reference) — every line was silently logged "No Perfdata. Skipping line" and no RRDs were ever created. Fix, in nagios.cfg (single-quoted YAML in the Ansible source — double-quotes would convert \t to a real tab and break Nagios's own escape parsing):

host_perfdata_file_template=DATATYPE::HOSTPERFDATA\tTIMET::$TIMET$\tHOSTNAME::$HOSTNAME$\t...
service_perfdata_file_template=DATATYPE::SERVICEPERFDATA\tTIMET::$TIMET$\tHOSTNAME::$HOSTNAME$\t...

Verify: real RRDs generate, e.g. rrd/localhost/Current_Users.rrd.


Phase 6 — NagiosQL / Nagios Core alignment incident

After completing the NagiosQL wizard and clicking "Write monitoring data," Nagios Core crash-looped (systemctl status nagios showed activating (auto-restart), restart counter over 190).

Root cause: NagiosQL's freshly-written config collided with Nagios Core's own stock quickstart sample files (windows.cfg, printer.cfg, switch.cfg, localhost.cfg, templates.cfg, installed by make install-config and never removed by the playbook) — identical sample host names and template names defined in both places at once.

Fixed:

  1. Retired the 5 stock sample files (moved outside the recursively-scanned objects/ tree, kept for reference).
  2. Removed 6 redundant cfg_dir entries the playbook had added — make install-config's default cfg_dir=/usr/local/nagios/etc/objects/ is already recursive, so the extras double-loaded every file underneath them.
  3. Permission gap: NagiosQL (running as apache) recreates its managed subdirectories as apache:apache mode 750, blocking the nagios user (not in the apache group). A one-time chgrp wouldn't survive NagiosQL doing this again, so added mutual group membership instead — nagios added to the apache group (alongside the existing reverse membership).
  4. Duplicate check_ncpa command: the ncpa_client role's own command definition duplicated what NagiosQL's default command set already defines identically. Removed the role's copy — NagiosQL is authoritative for object definitions now.

Also fixed while completing the wizard: NagiosQL's DB-creation step needs genuine CREATE DATABASE/CREATE USER privileges, which MariaDB's socket-authenticated root can't grant from a web form by design. Created a temporary password-authenticated installer account for that one step only (revoke after handoff).

Verify: Total Warnings: 0, Total Errors: 0 after applying all four fixes and restarting.


Phase 7 — Migration data pull from the old box

Pulled a full read-only copy of /usr/local/nagios/etc/ from the old box (excluding the htpasswd file) — nothing changed on the old box; the transfer tarball was deleted afterward.

Most of the old box's objects/ tree is a decade of untouched .bkp/.bak files Nagios never actually loads (only 96 of 243 files under service/ even end in .cfg). What's actually live, per nagios.cfg's own directives:


objects/commands.cfg, contacts.cfg, timeperiods.cfg, templates.cfg, linux-box.cfg,
escalation.cfg, servicegroup.cfg, localhost.cfg, windows.cfg
service/*.cfg   (96 files, one per client/host)

Authoritative scope (confirmed via nagios -v, 0 errors):

Object Count
Hosts 50
Services 711
Host groups 5
Service groups 3
Contacts / contact groups 7 / 7
Commands 327
Time periods 6
Host / service escalations 50 / 717

6 pre-existing duplicate-service warnings found in the source itself (Nagios tolerates these — first definition wins — but flagged as real cruft, not carried forward silently): most notably "Load Average" on mail.tetrain.com independently redefined in three separate client config files.


Phase 8 — Bulk import into NagiosQL

Imported the full config (105 files: 9 base object files + 96 per-client host/service files) via NagiosQL's own HTTP import mechanism, driven directly (logged in as NagiosQL's own admin user) rather than clicking through 105 files by hand.

Reverse-engineering the import form — NagiosQL's PHP source comments use different names than the real HTML fields, cost a couple of failed attempts before finding the real ones:


mselValue1[]   -- multi-select file list (NOT chkMselValue1[] as the source comments imply)
chbValue1      -- overwrite checkbox (NOT chkChbValue1)
hidStatus=1    -- required hidden trigger field, normally set by JS on submit in the real UI

Two fresh bugs found and fixed during this pass:

  1. The 105 staged import files sat in /usr/local/nagios/etc/objects/objects/ — itself inside the recursively-scanned objects/ tree, so Nagios loaded both the raw staging copies and NagiosQL's freshly-written official config at once. Moved staging files to /usr/local/nagios/etc/_import_staging/ (outside the scan path).
  2. Ansible's own commands/pnp4nagios.cfg duplicated process-host-perfdata-file, which NagiosQL's own imported command set also defines — retired, same fix as the check_ncpa duplicate in Phase 6.
  3. One data bug from the source itself: the passive_service template imported with an empty register field (should default to '0') — Nagios treated it as a real service instance missing a host. Fixed via a direct UPDATE on tbl_servicetemplate (single row), then re-triggered NagiosQL's "Write monitoring data" to regenerate the file from the corrected DB state.

Confirmed live (nagios -v, then restart, nagiostats): 53 hosts / 724 services loaded (+3 hosts / +13 services vs. the old box's 50/711 — NagiosQL's own leftover seed hosts, cleaned up in Phase 9). Total Warnings: 0, Total Errors: 0.


Phase 9 — Seed-data cleanup

Deleted the 3 leftover NagiosQL seed hosts (winserver, hplj2605dn, linksys-srw224p) and their services/link-table rows.


# NagiosQL's delete-from-list action isn't in hosts.php itself -- it's generic shared logic
# in prepend_content.php, triggered by POSTing to the object's own admin page:
POST admin/hosts.php   modus=checkform&selModify=delete&hidListId=<id>   (one ID per request)

Found: deleting the DB rows alone left orphaned .cfg files on disk — NagiosQL's "Write" only regenerates files for objects that currently exist, it does not prune files for objects that no longer do. The 3 stale host/service .cfg files had to be removed manually before re-validating.

Result: exact match to the old box's authoritative counts — 50 hosts, 711 services, 0 errors, confirmed via nagiostats after restart.


Phase 10 — PNP4Nagios graph routing fix

Thruk's graph links (/pnp4nagios/graph?host=X) were 404ing straight from Apache — the request never reached PHP.

Root cause: the Apache config only had a bare Alias /pnp4nagios to the share directory, missing PNP4Nagios's documented short-URL rewrite convention that Thruk's links rely on.

First attempt (failed): a top-level RewriteRule per endpoint (graph, image, json, xml, popup) — didn't work, because mod_alias's Alias directive had already translated the URL to a filesystem path before mod_rewrite ever got a chance to run.

Actual fix — matched PNP4Nagios's own shipped reference config (etc/config_samples/http_server_apache/pnp4nagios.conf): the rewrite has to live inside the <Directory> block, operating on the per-directory URL-path, with one generic catch-all rule rather than one rule per endpoint:


<Directory "/usr/local/pnp4nagios/share">
    ...
    RewriteEngine On
    RewriteCond %{THE_REQUEST} ^GET\ /pnp4nagios/(.+)/(.*)\ HTTP
    RewriteCond %{DOCUMENT_ROOT}/%1 !-f
    RewriteCond %{DOCUMENT_ROOT}/%1 !-d
    RewriteRule ^(.+)$ index.php/$1 [L]
</Directory>

Verify: localhost (which has accumulated real perfdata) returns HTTP 200 on its graph URL. Freshly-imported hosts correctly show PNP4Nagios's own "perfdata directory does not exist" message until Nagios has actually run checks against them long enough to accumulate history — expected, not a bug.


Phase 11 — Perfdata pipeline silently broken

After the Phase 10 routing fix, graphs still failed for every freshly-imported real host, even minutes after their checks had clearly succeeded (has_been_checked=1 in status.dat).

Root cause: the Phase 8 NagiosQL import brought in its own process-host-perfdata-file/process-service-perfdata-file command definitions, pointing at /usr/local/pnp4nagios/var/{host,service}-perfdata (the vendor's documented convention). But nagios.cfg's own host_perfdata_file/service_perfdata_file directives (set by the Ansible pnp4nagios role) still pointed at /usr/local/nagios/var/{host,service}-perfdata — a different path entirely. Nagios wrote real perfdata to one file; the processing command bulk-read a different, stale one. No error anywhere — it just silently processed nothing, indefinitely.

Fixed by realigning nagios.cfg to the vendor-standard path that NagiosQL's imported commands already expect, rather than fighting NagiosQL's convention. After restart, force-scheduled an immediate check for all 50 hosts via the external command pipe (SCHEDULE_FORCED_HOST_CHECK) rather than waiting out the normal 30-minute check-spread windows.

Verify: all 50 real hosts + localhost now have RRD data and return HTTP 200 on their graph URLs.


Phase 12 — Check_MK evaluated, nothing to migrate

The old box also runs Check_MK at nagios.tetrain.com/check_mk — checked whether it needed migrating too.


# Check_MK 1.2.2p2 (2013) -- an old, non-OMD, Nagios-add-on-style install
cat main.mk    # all_hosts = [ ]  -- zero hosts configured natively

The web login does still work, but it was purely a Livestatus-based UI layered on the same Nagios core — not a separate monitoring system with its own data (matches the client's own recollection of it predating Check_MK's modern full product). Conclusion: nothing was missed — all real monitoring config already lived in the raw Nagios object files migrated in Phase 8. Decision: stick with Thruk, do not stand up modern Checkmk — that would be a materially different, much bigger addition (its own agents, host/service discovery), out of scope for this migration. Thruk is architecturally the same "Livestatus UI on the same core" pattern the client liked about the old Check_MK setup.


Phase 13 — Duplicate-service cleanup

7 duplicate-service warnings existed post-import (0 blocking — Nagios just uses the first definition and ignores the rest — but real cruft). Turned out to be three genuinely different root causes, not one kind of cruft — a blind "delete the duplicate" pass would have been wrong for 5 of the 7:

  1. Stale NagiosQL seed data (Swap Usage/Root Partition on localhost, 2 warnings) — NagiosQL's factory-default localhost checks were never removed after the real import. Verified the correct values against the old box's actual production config before deleting: kept Root Partition at 10%/5% and Swap Usage at 20/10 (real values), deleted the 20%/10% seed leftovers.
  2. Two genuinely different checks sharing a name (Website Status on reachingcriticalwill.org, NTP on tetrain.com, 2 warnings) — one custom check command, one generic NRPE fallback, both real. Deleting either would have dropped real coverage — renamed to disambiguate instead: Website Status - NRPE and NTP - Default. (First attempt used parentheses, e.g. NTP (Default) — parentheses are in Nagios's illegal_object_name_chars list, hit a real "illegal characters" error; fixed with dashes.)
  3. A real config bug inherited from the old box (Load Average on mail.tetrain.com via 3 unrelated client files, 3 warnings) — email.planetpci.com, mail21c.pahwa.com, and zm.wbtea.com each define their own Load Average check but also tag hostgroup_name Tetra servers; since mail.tetrain.com is a member of that hostgroup too, all three unintentionally applied to it as well. Confirmed accidental (not intended as hostgroup-wide) — removed the hostgroup_name link from all 3 so each stays scoped to its own host only.

Verify: Total Warnings: 0, Total Errors: 0, service restarted, 50 hosts active.

Learned along the way: NagiosQL's "Write monitoring data" button (butValue1) does not rewrite commands.cfg — only "Write additional data" (butValue2) does. Also, NagiosQL web sessions expire — a second write attempt after idle time needs a fresh login (POST to tfUsername/tfPassword) before it will actually save.


Phase 14 — Notification integrations rebuilt

Pulled the full SMS/Telegram/ihelpdesk scripts from the old box (only headers had been reviewed before) and rebuilt them on the new box, per the client's own credential-handling decisions (kept-vs-rotate calls made explicitly, not assumed):

SMSsms.sh/php (own number, one gateway), hero-sms.sh/php and VCS-sms.sh/php (a second contact group, a second gateway) copied over as-is, credentials kept per the client's call. Confirmed VCS-sms.php and hero-sms.php were byte-identical — the long-standing "VCS-sms.sh actually calls hero-sms.php instead of its own file" bug had been a no-op in practice, but the routing was fixed anyway for future-proofing. Open item: one gateway's credentials look like a demo/trial account — verifying it's still active was deferred by the client.

Telegram — kept the existing bot token per the client's call, but hardened how it's used: rewrote nagios_telegram.py to call Telegram's plain HTTP Bot API directly (dropping a third-party client library and an EOL Python-version pin that doesn't even exist on Rocky 9) and to read the token from a --token-file argument instead of a --token CLI argument:


#!/usr/bin/env python3
# Token read from --token-file, not passed as a CLI argument -- a value passed on the
# command line is visible to any local user via `ps aux` for the life of the process;
# a file readable only by the nagios user is not.
import argparse, json, urllib.request, urllib.parse

def send_notification(token, chat_id, message):
    url = f"https://api.telegram.org/bot{token}/sendMessage"
    data = urllib.parse.urlencode({"chat_id": chat_id, "text": message}).encode()
    req = urllib.request.Request(url, data=data, method="POST")
    with urllib.request.urlopen(req, timeout=15) as resp:
        result = json.load(resp)
    if not result.get("ok"):
        raise RuntimeError(f"Telegram API error: {result}")

Token now lives at /usr/local/nagios/etc/telegram_bot_token, mode 600, owned nagios — previously it sat in plaintext directly inside commands.cfg. Updated the corresponding tbl_command rows (host + service notify commands) to match, applied via a .sql file against NagiosQL's database rather than fighting nested-quoting on the CLI.

ihelpdesk ticketing/opt/ihelpdesk_alert.sh (HMAC-signed API calls) copied as-is, secret kept per the client's call.

Config validates clean (Total Warnings: 0, Total Errors: 0), Nagios restarted and active. Only the Telegram channel was actually live-tested (a real message sent and confirmed received) — SMS and ihelpdesk were deliberately left untested pending the client's own go-ahead, since firing them sends a real message/ticket.


Phase 15 — Parity validation: real gaps found and fixed

A full status check on the new box initially showed 685 of 713 services CRITICAL. Rather than assume this was entirely the expected pre-cutover IP-allowlist gap, every failure was categorized by its actual error message — good thing, because only 356 were the expected NRPE/firewall gap (client-side firewalls only trust 172.105.47.191, not the build box's temporary IP — resolves automatically once the IP Transfer happens). The other 329 were real gaps: custom/third-party plugins present on the old box outside the standard nagios-plugins package, never copied during migration.

Missing Affected checks Root cause / fix
check_dnsbl 302 Perl module Net::DNS missing -- dnf install perl-Net-DNS
check_ssl_cert 13 Plugin binary missing -- copied from old box
Crypt::X509 (used by check_ssl_validity) 6 CPAN-installed module, not RPM. First copied to the version-agnostic /usr/local/share/perl5/, but Perl 5.32's @INC only searches the version-specific /usr/local/share/perl5/5.32/ -- moved into the correct subdirectory
check_https_1 5 Plugin binary missing -- copied from old box
check_zimbra_outgoing_mail.sh 1 Runs via NRPE on the remote Zimbra host itself, not the Nagios server -- copied for parity but was never actually a local gap

All plugins individually verified working after the fix (real, correct output — e.g. check_ssl_cert returned a genuine "valid until Sep 8 2026" result). A full recheck of all 713 services was forced afterward; check_dnsbl in particular is slow (up to 302 checks × 60s timeout each), so results took several minutes to fully settle.

Practical implication: full service-level parity can't be definitively confirmed until after the IP actually transfers, since ~356 checks are structurally blocked by client firewalls until then — that was always the expected shape of this migration, not a new problem. What is confirmed now: every plugin this box needs is present and working, config is clean, and the only remaining "failures" are exactly the ones the IP Transfer is meant to resolve.


Known-incomplete items (cutover deliberately on hold)

The client has explicitly held the production cutover until the following week — nothing below has been executed against the production IP, and none of it should be, until that go-ahead is given.

  • The Linode IP Transfer itself (Phase 2's diagram) — not yet performed. This is the step that actually moves 172.105.47.191 onto the new box; everything else in this runbook is preparation for it.
  • Admin network access — both the Cloud Firewall and Apache's own Require ip directive are still restricted to a single session IP used during the build. A real admin IP/range needs to be supplied before the team can reach the box post-cutover.
  • TLS certificate CN/SAN mismatch — the current cert still identifies as nagios-rocky9-build.tetrain.com; needs regeneration for nagios.tetrain.com before cutover, same pattern as Phase 8.1/8.2 of the wiki.tetrain.com migration (see that project's runbook for the exact certbot --expand pattern if a vhost-ambiguity error comes up here too).
  • Hostname not yet renamed — still nagios-rocky9-build.tetrain.com; renaming to nagios.tetrain.com is a post-cutover step.
  • SMS gateway credential verification (Phase 14) — one gateway's credentials look like a demo/trial account; the client deferred confirming whether it's still active.
  • SMS and ihelpdesk notification channels — rebuilt and config-validated, but never actually fired live (only Telegram was), pending the client's go-ahead.
  • Old box decommissioning — kept fully built and powered on (not off yet, not deleted) at its real IP for rollback safety; a decision on when to power it off/decommission comes only after a validated cutover.