Read-only scan
Kernel & bootloader validation, reboot-required per distro, /boot space, SELinux/AppArmor, failed logins, journal errors, memory pressure, core services — plus every enabled unit systemd reports as failed.
LinuxVitals is an agentless Ansible collection that scans, opt-in self-heals, and reports on your Linux servers — severity-ranked findings, per-distro reboot and kernel detection, a baseline → postcheck comparison for real maintenance windows, and one self-contained HTML dashboard at the end of it.
ansible-galaxy collection install sameeralam3127.linux_vitals
Nothing is installed on managed hosts, and every check is read-only unless you explicitly opt in.
Kernel & bootloader validation, reboot-required per distro, /boot space, SELinux/AppArmor, failed logins, journal errors, memory pressure, core services — plus every enabled unit systemd reports as failed.
Used space and used inodes on every real filesystem, not just /boot — plus a mount that unexpectedly went read-only, which is usually the kernel reacting to an I/O error.
5-minute load per CPU, PSI stall percentages for CPU, memory and I/O, and the swap paging rate — so a host thrashing with free RAM stops reading as healthy.
pressure_stall_highEvery finding is info, warning or critical. Raise linux_vitals_fail_on_severity and only what matters fails a host — everything is still reported.
A check that could not run — journal unreadable, tool missing — raises an unknown finding with the reason, and that host never counts as healthy.
One restart attempt per enabled, failed systemd unit — and the report says whether it worked. Off by default; it never enables, installs, or masks a unit.
vitals_healOpt-in expiry, weak-signature, and obsolete-protocol checks — on disk and over the wire, including a served cert that no longer matches the file.
vitals_certsSnapshot before a maintenance window, snapshot after, correlated by a maintenance id: improved and regressed hosts, RAM deltas, kernel changes, new and resolved findings.
vitals_reportOne HTML file plus a versioned JSON report. No CDN, no server — search, sort, filter, and expand any host, offline, forever.
dashboard.html.j2Slack Block Kit, email, and a generic webhook — each independent, with credentials kept in .env or vault, never on the command line.
linux_vitals_fail_on_status fails the run on failing or regressed hosts — after reports and notifications are written, so the evidence always exists.
Rendered by the real vitals_report role from the collection's own golden scan fixtures: a baseline run, a patch window, and a postcheck with the same maintenance id. Search a hostname, sort a column, expand a host.
The same fleet as the dashboard above, re-scored live in your browser with the collection's own rules. Change the threshold, retune a finding, set a pipeline gate — and watch which hosts fail, what the health score does, and whether the run would exit non-zero.
linux_vitals_fail_on_severity
The default, info, fails a host on any finding.
linux_vitals_fail_on_status
The run fails when more hosts than the threshold fail (any_fail) or went from Pass to Fail since the baseline (regression).
linux_vitals_finding_severity_overrides
Only findings this sample fleet raises are listed. The number is how many hosts raise it.
So LinuxVitals asks each one in its own language, reports which source produced the answer, and never reads a broken check as "all clear". Pick a family to watch the probe run.
The bootloader gets the same care: grubby, grubenv, GRUB_DEFAULT, BLS entries and
systemd-boot are all resolved, so a host with the newest kernel installed but an older one set to boot is flagged before it reboots into a surprise.
Read the detection docs ↗
Three playbooks, one shared maintenance id. Click a step to replay it.
Run …linux_vitals.baseline with a linux_vitals_maintenance_id before your window opens. Full posture, snapshotted per host.
Patch, reboot, reconfigure — however you normally do it. LinuxVitals stays out of the way.
Run …linux_vitals.postcheck with the same id. The dashboard shows exactly what changed — per host, per finding.
Too many hosts for one run? Shard the fleet: every shard keeps the window's maintenance id, and from the next release
each one sets its own linux_vitals_snapshot_shard, so a shard id reused for a different set of hosts is refused
instead of silently merged into the comparison.
Sharding a large fleet ↗
No agents, no daemons, no database — just SSH and Ansible. Python 3.10+ and ansible-core 2.16+ on the control node.
# pulls community.general too, which the notification tasks need ansible-galaxy collection install sameeralam3127.linux_vitals # on an ansible-core 2.16 control node, pin community.general below 12 ansible-galaxy collection install sameeralam3127.linux_vitals 'community.general:<12'
collections: - name: sameeralam3127.linux_vitals version: ">=2.2.0"
# one-shot health check across the fleet ansible-playbook -i inventory.ini sameeralam3127.linux_vitals.healthcheck # before a maintenance window ansible-playbook -i inventory.ini sameeralam3127.linux_vitals.baseline \ -e linux_vitals_maintenance_id=CHG-2026-1012 # after it, for the before/after diff ansible-playbook -i inventory.ini sameeralam3127.linux_vitals.postcheck \ -e linux_vitals_maintenance_id=CHG-2026-1012
# Ubuntu, Rocky and Fedora containers: healthy, broken on purpose, healed git clone https://github.com/sameeralam3127/linux-vitals cd linux-vitals/demo && ./run.sh # remove every trace of it ./run.sh clean
Every option below is generated from the collection's own meta/argument_specs.yml, with the real defaults. Change what you need; only what differs from a default ends up in the output — and Share gives you a link that reopens this exact configuration.
Report paths and the .env lookup resolve from this file's directory, not from the playbook's.
Ansible's default is 5. Raising this is the cheapest fleet-scale win; match it to control-node cores and to what your bastion tolerates.
Needed to read /var/log/btmp, the journal, and grub.cfg. Without it those checks come back unknown. Set it in the inventory rather than with --become, which would also try to escalate on the control node during reporting.
Leave all off to run everything. Include reporting with any focused tag, or the run produces no output file.
Pulled live from GitHub releases and the commit log, with a built-in copy for when the API is out of reach.
Release milestones, with live progress from the issues in each. Nothing here is a commitment — the reasoning lives in docs/roadmap.md, and you can weigh in on any issue.
Install it, point it at an inventory, and open one HTML file.