Symptom-first response for skyhouse.dev
Find the row that matches your alert text or what you're seeing, jump to that card,
and work top to bottom: rule out the obvious → diagnose
→ fix. Most cards also flag when a symptom could mean a break-in
rather than a hiccup — when in doubt, run the 2-minute triage in §1 first.
Companion docs: the Cheat Sheet
(ports, paths, service map) and /home/plex/docs/server-context.md
(full orientation). Baseline captured 2026-05-20.
Run this whenever an alert has no obvious, self-inflicted cause — especially CPU, memory, or disk surprises. In March 2026 this server ran a cryptominer for 19 days; the full worked example is in /home/plex/incident-report-2026-03-03.md.
Look at the biggest CPU and memory consumers. You should recognize every one.
top # press 'q' to quit; live %CPU
ps aux --sort=-%cpu | head -15
ps aux --sort=-%mem | head -15
Normal on this box: containerd, dockerd, the 15 containers' processes, Plex Media Server / Plex Transcoder, immich, netdata; and from the desktop session Xorg, gnome-shell, chrome, plus the CLI tools claude, agy, zed. A random-named binary burning CPU — especially owned by a container user — is the classic miner signature. Note: ps %CPU is a lifetime average, not "right now"; trust top for instantaneous load.
Malware often deletes its own binary and dials out to a pool/C2.
# processes running from a deleted or /tmp binary: ls -la /proc/*/exe 2>/dev/null | grep -E 'deleted|/tmp/' # established outbound connections (sudo adds process names): sudo ss -tnp state established
Normal outbound: Plex's pub-sub server, update checks to Google / Cloudflare / npm / plex.tv, and LAN devices (e.g. a Chromecast on 192.168.1.x). A container talking to an unfamiliar IP on an odd high port is worth chasing.
sudo ss -tlnp # listening ports + owning process last -n 20 # recent logins sudo lastb | head -20 # recent FAILED logins crontab -l # plex user cron sudo ls -la /etc/cron.d /etc/cron.daily cat ~/.ssh/authorized_keys # every key should be yours sudo fail2ban-client status sshd # current SSH bans
Cross-check listening ports against the Cheat Sheet's port table. An unexpected listener, an unfamiliar SSH key, or a cron entry you didn't add are all red flags.
If something is in a container (docker top <name> shows the rogue process), the blast radius is usually that container — stop it (docker stop <name>) and rebuild from clean source. If a rogue process is on the host, or you find a host cron / SSH key you didn't create, treat it as a host compromise: isolate (pull the network), preserve evidence, and rebuild. When unsure, stop the suspect container — that is reversible and buys time.
Netdata texts about low space on /. The OS drive is a 120 GB SSD (/dev/sda3) — small, and it fills.
Did you just copy something large into /home/plex? Is a big Plex transcode or a media-processing job running right now? Those are self-inflicted, not failures.
df -h / sudo du -xhd1 / | sort -h | tail -12 # biggest dirs on the OS disk sudo du -xhd1 /var | sort -h | tail -12 docker system df # image / build-cache bloat journalctl --disk-usage
On this server the usual culprits, in order: Docker (/var/lib/docker is on the OS disk — ~21 GB of images plus a multi-GB build cache), /var/log, the systemd journal, old kernels (3 are installed), and the Plex transcoder's temp files if they land on /.
docker image prune # delete unused images docker builder prune # delete build cache (safe; rebuilds slower once) sudo journalctl --vacuum-size=200M sudo apt clean # clear downloaded .deb cache sudo apt autoremove --purge # remove old kernels & orphan packages
Avoid bare docker system prune -a — it also deletes images not tied to a running container, forcing big re-pulls. The durable fix is the larger primary disk: see /home/plex/docs/disk-cutover-guide.md.
If the growth has no explanation — not Docker, not logs, not media — a dropped payload or attack logging is possible. Run §1.
Media-processing jobs (Plex transcoding, Handbrake, the Google-Takeout scripts) write large temp files. If /tmp or the transcode dir fills, jobs fail mid-run.
A job legitimately in progress will use scratch space — that is expected. The concern is space that stays consumed with nothing running.
df -h / /tmp du -sh /tmp/* 2>/dev/null | sort -h | tail ls -lt /tmp
Plex transcodes to its Transcoder temporary directory (Plex settings → Transcoder, or TranscoderTempDirectory in Preferences.xml); if that points at /, a 4K transcode can eat many GB. A crashed job can also leave orphaned temp files behind.
Delete orphaned temp files that no live process owns. Finish or stop a stuck job. Longer term: point Plex's transcoder temp at a media drive with room (one of the multi-TB /media/plex* mounts), and lean on the planned bigger OS disk — expanding scratch space is the whole reason for the swap.
Repeated texts from cpu_watchdog.sh — it fires when a process holds >50% CPU for 10+ minutes and is not on its allowlist (Plex, immich, handbrake and system tasks are already allowlisted).
Did you start something heavy that simply isn't on the watchdog's allowlist — a manual ffmpeg, a big rsync, an immich library re-scan? If so it is benign; the fix is to add it to ALLOWLIST in /usr/local/bin/cpu_watchdog.sh.
top # instantaneous; find the PID ps -eo pid,ppid,user,%cpu,etime,comm --sort=-%cpu | head ps -ef --forest # who is the parent? docker stats --no-stream # per-container CPU
Identify the process, its user, and whether it lives in a container. The March cryptominer showed up exactly here: sustained high CPU by a process the watchdog did not recognize, running inside music-discovery-client.
If you cannot account for the process — unfamiliar name, odd user, running from a container — go straight to §1. A cryptominer is the single most likely cause of an unexplained sustained-CPU alert on a public-facing server.
Netdata warns on RAM or swap. This was the first symptom of the March incident (swap hit 98%). The box now has 62 GB RAM and ~35 GB swap, so genuine pressure is less common — but not impossible.
free -h ps aux --sort=-%mem | head -12 docker stats --no-stream
Benign: the desktop Chrome browser routinely holds several GB; many simultaneous Plex transcodes and immich machine-learning runs are memory-hungry by design. Closing browser tabs or letting jobs finish resolves it.
An unrecognized process high in the memory list — run §1.
You can reach plex.skyhouse.dev etc. on the home network, but they're dead from the internet (test from your phone on cellular). The internet→router→server path or DNS is broken. /etc/hosts has local overrides for some subdomains, which is exactly why "inside" can keep working while "outside" is down.
# 1. Public IP vs what DNS says (the recurring failure mode): curl -s ifconfig.me ; echo dig +short skyhouse.dev @1.1.1.1 # 2. Is the reverse proxy up? (80/443 reach nothing if not) docker ps | grep npm-app-1
If the public IP and the DNS A-record disagree, DNS is stale — the once-every-few-months event. The DDNS pipeline (ip-watch.sh) should fix it; if it hasn't, correct the record at Dynadot manually. The router is also reachable directly at the always-current hostname cw526dc.glddns.com.
If DNS is correct but it's still dead from outside: confirm the router port-forwards (WAN 80, 443, 32400, 2222 → 192.168.1.136) in the GL.iNet admin; confirm the modem is still in bridge mode (a modem reset can re-enable its router and double-NAT you, killing all inbound); and confirm the server still holds its DHCP-reserved 192.168.1.136.
Low risk here — this is almost always plumbing. But if DNS records were changed and you didn't change them, check the Dynadot account for unauthorized access.
An ssh / scp / rsync / git deploy to the server times out or is refused.
SSH is on 2222, not 22. A deploy tool defaulting to 22 will silently fail. ssh -p 2222 plex@skyhouse.dev.
# Did fail2ban ban your deploy host? (a few bad auths = 24h ban) sudo fail2ban-client status sshd # Is password auth currently off? (lock_ssh.sh sets this) sudo sshd -T | grep -i passwordauthentication
The most common non-obvious cause: a deploy script retried with a wrong key/password, tripped fail2ban (3 strikes), and got the source IP banned for 24h. The second: lock_ssh.sh was run, so password auth is off and a password-based deploy now needs a key.
sudo fail2ban-client set sshd unbanip <YOUR-IP>
/home/plex/unlock_ssh.sh # re-enable password auth, if needed
Also rule out: public IP changed (the deploy targets the hostname → §6), or sshd itself is down (systemctl is-active ssh from the console).
No SSH access even with the right port and credentials.
Port 2222? · fail2ban ban on your current IP (test from a different network, or check from the console)? · password auth turned off by lock_ssh.sh and you have no key enrolled? · sshd stopped? · the whole server down (§10)?
This server has a physical desktop (keyboard + monitor). From that local console you can always log in and run /home/plex/unlock_ssh.sh, sudo fail2ban-client set sshd unbanip <IP>, or sudo systemctl restart ssh — no network required. You cannot lock yourself out permanently.
Uptime Kuma alerts, or you notice a single subdomain failing while the rest are fine.
Map the failing site to its backend:
| Site | Backend | Port |
|---|---|---|
| plex.skyhouse.dev | plexmediaserver (systemd) | 32400 |
| photos.skyhouse.dev | immich_server | 2283 |
| music.skyhouse.dev | music-discovery-client / -server | 8000 / 8001 |
| vault.skyhouse.dev | vaultwarden (LAN-only) | 80 (internal) |
| share.skyhouse.dev | pairdrop | 3000 |
| btst.skyhouse.dev | sharedmoments | 5001 |
| monitor.skyhouse.dev | netdata (systemd) | 19999 |
| status.skyhouse.dev | uptime-kuma | 3001 |
# containerized service: docker ps -a | grep <name> # Up, or Exited? docker logs --tail 50 <name> # systemd service (Plex, netdata): systemctl status <unit>
docker start <name> # or, from its compose dir: cd /home/plex/<dir> && docker compose up -d sudo systemctl restart <unit> # for Plex / netdata
If it crashes again immediately: read the logs, check disk space (§2). If the affected service is music-discovery-client, recall its incident history — run §1 before assuming it's a benign crash.
Every service is unreachable, inside and out.
From a LAN device: does ping 192.168.1.136 answer? If no — the box is off, crashed, or off the network: check power, the network cable, and the router. If ping answers but services don't — treat as a stack problem (Docker daemon down? → check from the console). Check uptime for an unexpected reboot.
Most services auto-start (Docker restart: unless-stopped, systemd units). Once it's back, run the verification checklist on the Cheat Sheet to confirm every service and mount came back. Note: the media drives mount with nofail, so the system boots even if a drive is missing — verify mounts explicitly.
Not every text is a problem. These are routine:
| Alert | Meaning & response |
|---|---|
| "Plex update available" | From plex-check. Run sudo plex-update when convenient. |
| "Next.js update available" | From nextjs-check. Schedule a music-discovery-client rebuild — not urgent unless it's a security release. |
| "Public IP changed" | From ip-watch.sh. Informational — DNS updates itself. |
| fail2ban ban notices | Routine internet background noise. Only notable if the banned IP is yours (→ §7). |