Sometimes you don’t need a full monitoring stack. You don’t need Prometheus, Grafana, or even Uptime Kuma. You just want to SSH into your server and see if everything’s okay in two seconds.

That’s exactly what this script does. One file, zero dependencies, instant health report.

What It Checks

CheckWhat You See
🖥 SystemUptime and CPU load average
🧠 MemoryUsage percentage with warnings
💾 DiskAll mounted filesystems
🐳 DockerEvery container — running or dead
🔌 PortsWhat’s listening and which process owns it
🔒 SSLCertificate expiry for your domains

Everything is color-coded: green means good, yellow means warning, red means fix it now.

The Script

Create a file called stackcheck.js on your server:

#!/usr/bin/env node
const { execSync } = require('child_process');
const { connect } = require('tls');
const os = require('os');

const WARN = '\x1b[33m⚠\x1b[0m';
const OK = '\x1b[32m✓\x1b[0m';
const FAIL = '\x1b[31m✗\x1b[0m';
const BOLD = '\x1b[1m';
const RESET = '\x1b[0m';

function run(cmd) {
  try { return execSync(cmd, { encoding: 'utf8', timeout: 10000 }).trim(); }
  catch { return null; }
}

function header(title) { console.log(`\n${BOLD}── ${title} ──${RESET}`); }

function checkUptime() {
  header('System');
  const up = os.uptime();
  const days = Math.floor(up / 86400);
  const hours = Math.floor((up % 86400) / 3600);
  const load = os.loadavg();
  const cpus = os.cpus().length;
  const icon = load[0] > cpus ? FAIL : load[0] > cpus * 0.7 ? WARN : OK;
  console.log(`  ${OK} Uptime: ${days}d ${hours}h`);
  console.log(`  ${icon} Load: ${load.map(l => l.toFixed(2)).join(', ')} (${cpus} cores)`);
}

function checkMemory() {
  header('Memory');
  const total = os.totalmem();
  const free = os.freemem();
  const used = total - free;
  const pct = Math.round(used / total * 100);
  const icon = pct >= 90 ? FAIL : pct >= 75 ? WARN : OK;
  const gb = n => (n / 1073741824).toFixed(1);
  console.log(`  ${icon} ${gb(used)}G / ${gb(total)}G (${pct}%)`);
}

function checkDisk() {
  header('Disk');
  const out = run("df -h --output=target,size,used,avail,pcent -x tmpfs -x devtmpfs -x overlay 2>/dev/null || df -h");
  if (!out) return console.log(`  ${WARN} Could not check disk`);
  for (const [i, line] of out.split('\n').entries()) {
    if (i === 0) { console.log(`  ${line}`); continue; }
    const pct = parseInt((line.match(/(\d+)%/) || [])[1]);
    console.log(`  ${pct >= 90 ? FAIL : pct >= 75 ? WARN : OK} ${line}`);
  }
}

function checkDocker() {
  header('Docker');
  const out = run('docker ps -a --format "{{.Names}}\\t{{.Status}}\\t{{.Image}}"');
  if (!out) return console.log(`  ${WARN} Docker not available`);
  let up = 0, down = 0;
  for (const line of out.split('\n')) {
    const [name, status, image] = line.split('\t');
    const running = status.toLowerCase().startsWith('up');
    running ? up++ : down++;
    console.log(`  ${running ? OK : FAIL} ${name.padEnd(30)} ${status}`);
  }
  console.log(`\n  Up: ${up} | Down: ${down}`);
}

function checkPorts() {
  header('Ports');
  const out = run("ss -tlnp 2>/dev/null | tail -n +2") || run("netstat -tlnp 2>/dev/null | tail -n +2");
  if (!out) return console.log(`  ${WARN} Could not check ports`);
  const ports = new Map();
  for (const line of out.split('\n')) {
    const m = line.match(/:(\d+)\s/);
    const proc = (line.match(/users:\(\("([^"]+)"/) || [])[1] || '';
    if (m) ports.set(m[1], proc);
  }
  for (const [port, proc] of [...ports].sort((a, b) => a[0] - b[0])) {
    console.log(`  ${OK} :${port.padEnd(8)} ${proc}`);
  }
}

async function checkSSL(domains) {
  if (!domains.length) return;
  header('SSL Certificates');
  for (const domain of domains) {
    await new Promise(resolve => {
      const sock = connect(443, domain, { servername: domain, timeout: 5000 }, () => {
        const cert = sock.getPeerCertificate();
        sock.end();
        const days = Math.round((new Date(cert.valid_to) - Date.now()) / 86400000);
        const icon = days <= 7 ? FAIL : days <= 30 ? WARN : OK;
        console.log(`  ${icon} ${domain.padEnd(30)} ${days} days left`);
        resolve();
      });
      sock.on('error', () => { console.log(`  ${FAIL} ${domain.padEnd(30)} failed`); resolve(); });
    });
  }
}

async function main() {
  console.log(`${BOLD}🔍 stackcheck${RESET}${new Date().toISOString()}\n`);
  const domains = process.argv.slice(2).filter(a => !a.startsWith('-'));
  checkUptime();
  checkMemory();
  checkDisk();
  checkDocker();
  checkPorts();
  await checkSSL(domains);
  console.log(`\n${BOLD}Done.${RESET}`);
}

main();

Running It

# Make it executable
chmod +x stackcheck.js

# Basic health check
./stackcheck.js

# Include SSL certificate checks
./stackcheck.js yourdomain.com nextcloud.yourdomain.com

That’s it. No npm install, no Docker image, no config files.

What the Output Looks Like

🔍DosUntSMDDpPSeayULe4i/ooso:o:::Sy.cspom.sclepr248Lokttao9kklae2t240uceidrGearns38Crhmm:yrmx-|0edee/3anwroc:0.geDtmk.17boia501Tuwfi429inind,.:snpc.271sgyac020G.1hitto20.5dnhem6h0(Txos-24n0,%UUE2)2ppx-0.i1.133t90TeT0wwd0ee9(4ee(:22kk100%ss307:c)0o0r8.e80sw40)e0edZkasysagloeft

Warning Thresholds

The script uses simple, sensible thresholds:

  • Memory/Disk: ✓ under 75% → ⚠ 75-89% → ✗ 90%+
  • CPU Load: ✓ under 70% of cores → ⚠ 70-100% → ✗ over 100%
  • SSL: ✓ 30+ days → ⚠ 8-30 days → ✗ 7 days or less
  • Docker: ✓ running → ✗ exited/dead

Automate It with Cron

Run it every 6 hours and log the output:

# Add to crontab
crontab -e

# Every 6 hours, check and log
0 */6 * * * /usr/bin/node /opt/stackcheck.js yourdomain.com >> /var/log/stackcheck.log 2>&1

Want email alerts? Pipe it through mail:

0 */6 * * * /usr/bin/node /opt/stackcheck.js yourdomain.com | mail -s "Stack Health Report" [email protected]

When to Use This vs. Full Monitoring

Use stackcheck when:

  • You manage 1-3 servers and want a quick glance
  • You’re SSHing in anyway and want a health summary
  • You don’t want to maintain a monitoring stack
  • You need something that works on minimal installs

Use Uptime Kuma / Prometheus / Grafana when:

  • You need historical data and graphs
  • You need real-time alerting to Slack/Discord/Telegram
  • You’re monitoring dozens of services across multiple hosts
  • You need public status pages

There’s no reason you can’t use both. Run stackcheck for quick manual checks and Uptime Kuma for ongoing monitoring.

Extending It

The script is intentionally simple. Fork it and add whatever you need:

  • Backup age check: stat your backup directory, warn if older than 24h
  • Service-specific health: curl your Nextcloud/Jellyfin health endpoints
  • Temperature: read /sys/class/thermal/thermal_zone*/temp on bare metal
  • SMART disk health: parse smartctl output

The whole point is that it’s one file you understand completely. No framework, no abstractions, no surprises.

Wrapping Up

Monitoring doesn’t have to be complicated. Sometimes the best tool is a script you can read top to bottom in five minutes. Copy stackcheck.js to your server, run it, and know exactly where you stand.