Introduction
This project is a simple Bash-based Linux Health Checks monitoring script.
It collects server metrics such as CPU, memory, disk, swap, and URL status, then generates an HTML dashboard report indicating whether the system is OK, Warning, or Critical.
Prerequisites
- Linux system (Ubuntu/CentOS/RHEL)
- Basic Bash scripting knowledge
- Commands:
top,free,df,curl,awk - Web browser to view the HTML report
ALSO READ:
- Complete Install Kubernetes with Kind (Kubernetes IN Docker) on RHEL 9 / CentOS Stream 9
- AWS S3 Backups with This Efficient Shell Script
- Bash Brackets Explained in Simple Words (With 8 Examples)
Click here to go to the GitHub repos link
Features
- CPU usage check
- Memory usage check
- Swap usage check
- Disk utilization check
- URL/website status check
- Automatic status classification (OK / Warning / Critical)
- Interactive HTML dashboard with filters and details
- Can be scheduled using cron
Health Checks Work:
- Collects system data using Linux commands
- Compares values with thresholds
- Assigns health status
- Generates a styled HTML report
Health Check Shell Script
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Health checks</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" integrity="sha512-Kc323vGBEqzTmouAECnVceyQqyqdsSiqLQISBL29aUW4U/M7pSPA/gEUZQqv1cwx4OnYxTxve5UMg5GT6L4JJg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
/* ---------- Base ---------- */
body {
font-family: Arial, sans-serif;
padding: 40px;
padding-top: 5px;
background-color: #F4F4F4;
color: #222;
}
/* Top header row */
.header-row {
width: 100%;
overflow: hidden;
font-weight: bold;
color: #444;
}
.left { float: left; }
.right { float: right; }
hr {
border: none;
border-top: 1px solid #E0E0E0;
margin: 12px 0 20px;
}
/* ---------- Title box ---------- */
.box {
width: 550px;
margin: 0 auto; /* centers the box */
padding: 16px 20px;
border: 2px solid #0078D4; /* Microsoft blue border */
border-radius: 10px;
text-align: center;
font-size: 20px;
font-weight: bold;
background: #FFFFFF;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
color: #222;
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.box:hover {
border-color: #005A9E; /* slightly darker blue */
box-shadow: 0 8px 20px rgba(0,0,0,0.25);
transform: translateY(-2px);
cursor: default; /* or pointer if you plan to make it clickable */
}
/* ---------- THREE BOXES SIDE BY SIDE (equal size) ---------- */
.row3 {
display: flex;
gap: 16px;
align-items: stretch; /* equal visual height */
flex-wrap: nowrap; /* keep side-by-side */
width: 100%;
}
/* Make all three boxes equal width/height and nicely aligned */
.instance-box,
.status-box,
.summary-box {
flex: 1; /* equal width */
min-width: 250px;
display: flex;
flex-direction: column;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0,0,0,0.12);
padding: 16px 20px;
}
/* Instance Box (left) */
.instance-box {
border: 2px solid #0078D4;
background: #FFFFFF;
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.instance-box:hover {
border-color: #005A9E;
background-color: #F3F8FF; /* very light blue */
box-shadow: 0 8px 20px rgba(0,0,0,0.20);
transform: translateY(-2px);
cursor: default; /* use pointer if you make it clickable later */
}
.instance-box .meta {
text-align: left;
font-size: 14px;
line-height: 1.45;
white-space: nowrap;
margin: auto 0; /* vertical centering of content */
}
/* Overall Health (center) - base */
.status-box {
border: 2px solid #F59E0B;
background: #FFF7ED;
color: #9A3412;
font-weight: 600;
text-align: center;
justify-content: center; /* vertical centering */
align-items: center; /* horizontal centering */
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.status-box:hover {
box-shadow: 0 8px 20px rgba(0,0,0,0.20);
transform: translateY(-2px);
cursor: default;
}
.status-box .label {
display: block;
font-size: 13px;
text-transform: uppercase;
margin-bottom: 6px;
}
.status-box .value {
font-size: 22px;
font-weight: 800;
}
/* Overall Health variants (auto-applied) */
.status-box.ok {
border-color: #0FA958;
background: #E6F9EE;
color: #065F46;
}
.status-box.warning {
border-color: #F59E0B;
background: #FFF7ED;
color: #9A3412;
}
.status-box.critical {
border-color: #D62828;
background: #FEECEC;
color: #7F1D1D;
}
/* Summary Box (right) */
.summary-box {
border: 2px solid #0078D4; /* Blue border */
background: #FFFFFF;
color: #222;
font-weight: 600;
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.summary-box:hover {
border-color: #005A9E;
background-color: #F3F8FF; /* very light blue */
box-shadow: 0 8px 20px rgba(0,0,0,0.20);
transform: translateY(-2px);
cursor: default;
}
.summary-box .label {
display: block;
font-size: 15px;
text-transform: uppercase;
margin-bottom: 10px;
color: #0078D4; /* Blue heading */
}
/* Summary filter buttons (side-by-side) */
.summary-actions {
display: flex;
gap: 8px;
flex-wrap: wrap; /* wrap if narrow */
align-items: center;
}
.summary-filter {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
font-size: 14px;
border-radius: 999px; /* pill */
border: 1px solid #D1D5DB;
background: #F8FAFC;
color: #111827;
cursor: pointer;
user-select: none;
outline: none;
}
.summary-filter .dot {
width: 10px; height: 10px; border-radius: 50%;
display: inline-block;
}
.summary-filter.ok { color: #065F46; background: #ECFDF5; border-color: #A7F3D0; }
.summary-filter.warning { color: #92400E; background: #FFF7ED; border-color: #FED7AA; }
.summary-filter.critical { color: #7F1D1D; background: #FEF2F2; border-color: #FECACA; }
.summary-filter.active { box-shadow: 0 0 0 2px #0078D4 inset; }
/* ---------- Health table ---------- */
.health-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
background: #fff;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}
.health-table th {
background: #0078D4; /* Microsoft blue */
color: white;
padding: 12px;
text-align: left;
font-size: 15px;
}
.health-table td {
padding: 12px;
font-size: 14px;
color: #333;
border-bottom: 1px solid #EEE;
}
/* Status badges inside table */
.status-ok {
color: #0FA958;
background: #E6F9EE;
padding: 4px 10px;
border-radius: 5px;
display: inline-block;
font-weight: bold;
}
.status-warning {
color: #E29A00;
background: #FFF4E0;
padding: 4px 10px;
border-radius: 5px;
display: inline-block;
font-weight: bold.
}
.status-critical {
color: #D62828;
background: #FEECEC;
padding: 4px 10px;
border-radius: 5px;
display: inline-block;
font-weight: bold;
}
/* Buttons (used by Details) */
.detail-btn {
box-shadow: 1px 1px black;
border-radius: 15px;
padding: 10px 16px;
font-size: 16px;
cursor: pointer;
border: 1px solid #D1D5DB;
background: #E5E7EB;
}
button:hover, .detail-btn:hover {
background: #D1D5DB;
}
/* ====== ONLY CHANGE ADDED EARLIER: Color styling for the Details button ====== */
.detail-btn {
background-color: #0078D4 !important; /* Microsoft blue */
color: #FFFFFF !important;
border: none !important;
border-radius: 6px !important;
padding: 8px 16px !important;
font-weight: bold;
}
.detail-btn:hover {
background-color: #005A9E !important; /* darker blue on hover */
}
/* ====== END CHANGE ====== */
/* ---------- Inline details rows inside the table ---------- */
.details-row { display: none; }
.details-row.show { display: table-row; }
.details-row td {
padding: 0;
background: #FAFAFA;
border-bottom: 1px solid #EEE;
}
.details-content { padding: 12px 16px; }
/* Inner table inside details */
.inner-table {
width: 100%;
border-collapse: collapse;
margin-top: 8px;
background: #fff;
border: 1px solid #EEE;
border-radius: 8px;
overflow: hidden;
}
.inner-table th {
background: #F3F4F6;
padding: 8px 10px;
text-align: left;
font-size: 14px;
color: #111827;
}
.inner-table td {
padding: 8px 10px;
border-top: 1px solid #F1F1F1;
color: #333;
}
.footer-bar {
background: #E5E7EB;
padding: 10px;
text-align: center;
border-radius: 8px;
margin-top: 20px;
font-size: 14px;
font-weight: bold;
}
/* .top-header {
background: linear-gradient(to right, #0078D4, #00A1FF);
color: white;
padding: 20px;
border-radius: 12px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
} */
.top-header {
padding: 15px;
display: flex;
justify-content: space-between;
margin-bottom: 20px;
box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.4);
}
/* hover effect (optional) */
.top-header img:hover {
transform: scale(1.05);
}
.health-table td i {
margin-right: 8px;
color: #6c757d; /* muted gray */
opacity: 0.9;
}
/* ====== ONLY ADDED: Hover colors for Summary buttons ====== */
.summary-filter.ok:hover {
background:#D1FAE5 !important;
border-color:#6EE7B7 !important;
}
.summary-filter.warning:hover {
background:#FFE8C7 !important;
border-color:#FBBF77 !important;
}
.summary-filter.critical:hover {
background:#FEE2E2 !important;
border-color:#FCA5A5 !important;
}
/* Server heading style */
.server-heading {
font-size: 16px;
font-weight: bold;
margin-bottom: 6px;
color: #0078D4; /* Microsoft blue */
}
/* ====== END ====== */
</style>
</head>
<body>
<div class="top-header">
<div><img src="C:\Devsecops\repos\test/google.png" alt="Google Logo" height="30"></div>
<div><img src="C:\Devsecops\repos\test/tech_base_hub.png" alt="Tech Base Hub Logo" height="30"></div>
</div>
<hr>
<!-- Title box -->
<div class="box">
Health Checks Report - tech-k8s-1.com
</div>
<hr>
<!-- THREE BOXES SIDE BY SIDE (equal size) -->
<div class="row3">
<!-- LEFT -->
<div class="instance-box">
<div class="meta">
<div class="server-heading">Server Details</div>
<div><b>Instance:</b>tech-k8s-1.com</div>
<div><b>Time Stamp:</b>Sat Feb 7 05:47:10 PM IST 2026</div>
</div>
</div>
<!-- CENTER: Overall Health (auto) -->
<div id="overallBox" class="status-box">
<span class="label">Overall Health</span>
<span id="overallValue" class="value">—</span>
</div>
<!-- RIGHT: Summary with side-by-side filter buttons -->
<div class="summary-box">
<span class="label">Summary</span>
<div class="summary-actions">
<button class="summary-filter ok" data-filter="ok" aria-pressed="false">
<span class="dot" style="background:#0FA958"></span>
OK : <span id="okCount">0</span>
</button>
<button class="summary-filter warning" data-filter="warning" aria-pressed="false">
<span class="dot" style="background:#E29A00"></span>
Warning : <span id="warnCount">0</span>
</button>
<button class="summary-filter critical" data-filter="critical" aria-pressed="false">
<span class="dot" style="background:#D62828"></span>
Critical : <span id="critCount">0</span>
</button>
</div>
</div>
</div>
<hr>
<!-- HEALTH TABLE -->
<table class="health-table" id="healthTable">
<tr>
<th>S.No</th>
<th>Check</th>
<th>Status</th>
<th>Details</th>
</tr>
<!-- CPU -->
<tr class="data-row" data-status="ok">
<td>1</td>
<td><i class="fas fa-microchip"></i> <b>CPU Usage</b></td>
<td><span class="status-ok">OK</span></td>
<td><button class="detail-btn">Details</button></td>
</tr>
<tr class="details-row">
<td colspan="4">
<div class="details-content">
<h4><u>CPU Usage</u></h4>
<table class="inner-table">
<tr><th>Processor %</th><th>User %</th><th>System %</th><th>Idle %</th></tr>
<tr><td>3.1</td><td>3.1</td><td>0.0</td><td>93.8</td></tr>
</table>
</div>
</td>
</tr>
<!-- Memory -->
<tr class="data-row" data-status="critical">
<td>2</td>
<td><i class="fas fa-memory"></i> <b>Memory Usage</b></td>
<td><span class="status-critical">CRITICAL</span></td>
<td><button class="detail-btn">Details</button></td>
</tr>
<tr class="details-row">
<td colspan="4">
<div class="details-content">
<h4><u>Memory Usage</u></h4>
<table class="inner-table">
<tr><th></th><th>Total</th><th>Used</th><th>Free</th><th>Shared</th><th>Buff/Cache</th><th>Available</th></tr>
<tr><th>Mem:</th><td>3.8Gi</td><td>2.1Gi</td><td>221Mi</td><td>42Mi</td><td>1.8Gi</td><td>1.7Gi</td></tr>
</table>
</div>
</td>
</tr>
<!-- Swap -->
<tr class="data-row" data-status="ok">
<td>3</td>
<td><i class="fas fa-sd-card"></i> <b>Swap Usage</b></td>
<td><span class="status-ok">OK</span></td>
<td><button class="detail-btn">Details</button></td>
</tr>
<tr class="details-row">
<td colspan="4">
<div class="details-content">
<h4><u>Swap Usage</u></h4>
<table class="inner-table">
<tr><th></th><th>Total</th><th>Used</th><th>Free</th></tr>
<tr><th>Swap:</th><td>4.0Gi</td><td>0B</td><td>4.0Gi</td></tr>
</table>
</div>
</td>
</tr>
<!-- Disk -->
<tr class="data-row" data-status="warning">
<td>4</td>
<td><i class="fas fa-hard-drive"></i> <b>Disk Usage</b></td>
<td><span class="status-warning">WARNING</span></td>
<td><button class="detail-btn">Details</button></td>
</tr>
<tr class="details-row">
<td colspan="4">
<div class="details-content">
<h4><u>Disk Usage</u></h4>
<table class="inner-table">
<tr><th>Filesystem</th><th>Type</th><th>Size</th><th>Used</th><th>Avail</th><th>Use%</th></th><th>Mounted on</th></tr>
<tr><td>devtmpfs</td><td>devtmpfs</td><td>4.0M</td><td>0</td><td>4.0M</td><td>0%</td><td>/dev</td></tr>
<tr><td>tmpfs</td><td>tmpfs</td><td>2.0G</td><td>0</td><td>2.0G</td><td>0%</td><td>/dev/shm</td></tr>
<tr><td>tmpfs</td><td>tmpfs</td><td>782M</td><td>9.9M</td><td>772M</td><td>2%</td><td>/run</td></tr>
<tr><td>/dev/mapper/almalinux_tech--k8s--1-root</td><td>xfs</td><td>35G</td><td>13G</td><td>23G</td><td>36%</td><td>/</td></tr>
<tr><td>/dev/nvme0n1p1</td><td>xfs</td><td>960M</td><td>481M</td><td>480M</td><td>51%</td><td>/boot</td></tr>
<tr><td>tmpfs</td><td>tmpfs</td><td>391M</td><td>52K</td><td>391M</td><td>1%</td><td>/run/user/42</td></tr>
<tr><td>tmpfs</td><td>tmpfs</td><td>391M</td><td>36K</td><td>391M</td><td>1%</td><td>/run/user/1000</td></tr>
</table>
</div>
</td>
</tr>
<!-- URL -->
<tr class="data-row" data-status="ok">
<td>5</td>
<td><i class="fas fa-link"></i> <b>URL Status</b></td>
<td><span class="status-ok">OK</span></td>
<td><button class="detail-btn">Details</button></td>
</tr>
<tr class="details-row">
<td colspan="4">
<div class="details-content">
<h4><u>URL Status</u></h4>
<table class="inner-table">
<tr><td><pre>HTTP/2 200
content-type: text/html; charset=ISO-8859-1
content-security-policy-report-only: object-src 'none';base-uri 'self';script-src 'nonce-33P0a7iY1rYm62UtHSAXNg' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp
reporting-endpoints: default="//www.google.com/httpservice/retry/jserror?ei=Ri2HaamNHqXBkPIP1t3uuA8&cad=crash&error=Page%20Crash&jsel=1"
accept-ch: Sec-CH-Prefers-Color-Scheme
p3p: CP="This is not a P3P policy! See g.co/p3phelp for more info."
date: Sat, 07 Feb 2026 12:17:10 GMT
server: gws
x-xss-protection: 0
x-frame-options: SAMEORIGIN
expires: Sat, 07 Feb 2026 12:17:10 GMT
cache-control: private
set-cookie: AEC=AaJma5u3Mo-ZBegip02Lelrw5S1quFB3BnGJrH2FUIH2Fw_EQQlaxj2kSV4; expires=Thu, 06-Aug-2026 12:17:10 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax
set-cookie: NID=528=ev6g1EKIwPvTIDZ5tPZPiCmUrutO-sYsEXkAAiW_o88IO_l6WuCWgeDzWPiT_HGUGHY6r_wMErH6B1rWdzIOa1OeYjI0-RYHGH9AoJbhh6WZWqftEJIkFjWji401CiRiu5-J66ic-rDe0mtcG4Hxr7OBDLXFyqx-PJRcHfsdM_1BT67sleao5NNmRxB6EVQ8P6NlDOY1zTyMDTcI4Vk_L1ZM5P5v_Q; expires=Sun, 09-Aug-2026 12:17:10 GMT; path=/; domain=.google.com; HttpOnly
set-cookie: __Secure-BUCKET=CP8B; expires=Thu, 06-Aug-2026 12:17:10 GMT; path=/; domain=.google.com; Secure; HttpOnly
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
</pre></td></tr>
</table>
</div>
</td>
</tr>
</table>
<!-- Modern Footer -->
<footer class="footer-bar">
© 2026 Tech Base Hub. All rights reserved.
</footer>
<script>
/* ---------- Inline Details Toggle ---------- */
document.addEventListener('click', function (e) {
const btn = e.target.closest('.detail-btn');
if (!btn) return;
const tr = btn.closest('tr');
const detailsRow = tr && tr.nextElementSibling;
if (!detailsRow || !detailsRow.classList.contains('details-row')) return;
const isShown = detailsRow.classList.toggle('show');
// FIX: sync inline 'display' with .show class so filters can't suppress it
detailsRow.style.display = isShown ? 'table-row' : 'none';
btn.textContent = isShown ? 'Hide' : 'Details';
// Keep only one details row open at a time
document.querySelectorAll('.details-row.show').forEach(row => {
if (row !== detailsRow) {
row.classList.remove('show');
row.style.display = 'none'; // FIX: ensure others are actually hidden
const otherBtn = row.previousElementSibling?.querySelector('.detail-btn');
if (otherBtn) otherBtn.textContent = 'Details';
}
});
});
/* ---------- Summary Counts, Filters & Overall Health ---------- */
(function(){
let activeFilter = null; // 'ok' | 'warning' | 'critical' | null
const table = document.getElementById('healthTable');
const dataRows = () => Array.from(table.querySelectorAll('tr.data-row'));
// Count statuses from the table
function getCounts() {
const counts = { ok: 0, warning: 0, critical: 0 };
dataRows().forEach(tr => {
const st = tr.getAttribute('data-status');
if (counts[st] !== undefined) counts[st]++;
});
return counts;
}
// Update the Summary numbers
function updateSummaryCounts(counts) {
document.getElementById('okCount').textContent = counts.ok;
document.getElementById('warnCount').textContent = counts.warning;
document.getElementById('critCount').textContent = counts.critical;
}
// Set Overall Health by counts with tie-break: critical > warning > ok
function updateOverallHealth(counts) {
const overallBox = document.getElementById('overallBox');
const overallText = document.getElementById('overallValue');
let state = 'ok';
if (counts.critical > 0) {
state = 'critical';
} else if (counts.warning > 0) {
state = 'warning';
} else {
state = 'ok';
}
overallText.textContent = state.toUpperCase();
overallBox.classList.remove('ok', 'warning', 'critical');
overallBox.classList.add(state);
}
// Apply/toggle filter driven by Summary clicks
function applyFilter(filterKey) {
if (activeFilter === filterKey) {
activeFilter = null; // toggle off -> show all
} else {
activeFilter = filterKey;
}
// Active state styling
document.querySelectorAll('.summary-filter').forEach(btn => {
const f = btn.getAttribute('data-filter');
const isActive = (activeFilter === f);
btn.classList.toggle('active', isActive);
btn.setAttribute('aria-pressed', isActive ? 'true' : 'false');
});
// Show/hide rows based on filter (and manage their details row)
dataRows().forEach(tr => {
const status = tr.getAttribute('data-status');
const show = !activeFilter || status === activeFilter;
tr.style.display = show ? '' : 'none';
const det = tr.nextElementSibling;
if (det && det.classList.contains('details-row')) {
if (!show) {
det.classList.remove('show');
det.style.display = 'none';
const btn = tr.querySelector('.detail-btn');
if (btn) btn.textContent = 'Details';
} else {
// If row is visible and details were open, display them
det.style.display = det.classList.contains('show') ? 'table-row' : 'none';
}
}
});
}
// Wire summary buttons
document.querySelectorAll('.summary-filter').forEach(btn => {
btn.addEventListener('click', () => applyFilter(btn.getAttribute('data-filter')));
});
// Initial compute
const counts = getCounts();
updateSummaryCounts(counts);
updateOverallHealth(counts);
})();
</script>
</body>
</html>
Note: Please update thresholds, paths, and URLs as per your server environment before running the script.
