linux

Debugs and hardens Linux hosts: permissions, disk full, OOM kills, stuck processes, systemd units, cron, networking, SSH, and boot failures. Use when a service starts by hand but f…

Iván

@ivangdavila

Install

$ openclaw skills install @ivangdavila/linux

User preferences and memory live in ~/Clawic/data/linux/ (see setup.md on first use, memory-template.md for the file format). If you have data at an old location (~/linux/ or ~/clawic/linux/), move it to ~/Clawic/data/linux/.

When To Use

  • Diagnosing permission denials, disk full, OOM kills, unkillable processes, or services that fail only at boot
  • Running or reviewing operations that touch permissions, signals, systemd units, scheduled jobs, packages, or firewalls
  • Changing configuration on a remote host without locking yourself out, and recovering one that will not boot
  • Reading system tools whose output misleads: free, df, top, %util, load average
  • Hardening an exposed host: SSH, firewall, MAC, accounts, auditing
  • Not for shell scripting syntax (bash), container build and runtime internals (docker), or cluster scheduling (k8s)

Quick Reference

SymptomFirst move
"Permission denied" though the mode bits look rightnamei -l <path>; then ACL (+ in ls -l), SELinux (ls -Z), mount options (findmnt -T) → permissions.md
Root itself gets "permission denied"lsattr (immutable), ls -Z (SELinux), getcap — root is not omnipotent (rule 8)
Denied only when it runs as a serviceUnit sandboxing: systemd-analyze security <unit>, then ReadWritePaths=systemd.md
df says full, du cannot find itlsof +L1 for deleted-but-open files, then the bind-mount check → disk-space.md
"No space left on device" with free space in df -hdf -i for inodes; if it came from a file watcher it is the inotify limit → kernel.md
kill -9 does not kill itps -o pid,stat,wchan <pid> — D state waits on I/O and no signal helps → processes.md
Exit code 137, or the OOM killer fireddmesg -T | grep -i oom; cgroup limit vs host exhaustion → memory.md
Host swaps and crawls but nothing diesvmstat 1 — sustained si/so is thrash, worse than an OOM kill → memory.md
Service starts by hand, fails at bootOrdering (network-online.target) and environment (absolute paths) → systemd.md
Unit gives up: "start request repeated too quickly"Start limit — add RestartSec=, then systemctl reset-failedsystemd.md
Job runs in your shell, fails under cronMinimal PATH, no profile, % is special → scheduling.md
SSH key suddenly rejected, no error client-sidePerms 700/600 and a home that is not group-writable; journalctl -u sshd -fssh.md
About to change sshd, sudoers, firewall, or fstab remotelyRule 4: second session, scheduled rollback, validator → ssh.md
Host does not boot, or drops to an emergency shellIdentify the stage first; usually fstab → boot.md
Port unreachabless -tlnp (bound to 127.0.0.1?), then the firewall front end, then the route → networking.md
dig resolves but the application cannotApplications go through NSS, dig does not — getent hostsnetworking.md
Large transfers hang, small requests fineMTU black hole: ping -M do -s 1472 <host>networking.md
Load average high, CPU mostly idleI/O wait and D-state inflate load — a storage problem → performance.md
Latency spikes with the host CPU idlecgroup CPU throttling: cpu.stat nr_throttledperformance.md
Upgrade broke or was interrupteddpkg --configure -a / dnf history undo; never kill a running package transaction → packages.md
Old code still running after an upgradeneedrestart / dnf needs-restarting -r; kernel needs a reboot → packages.md
New user cannot sudo, or login failsid, sudo -l -U, chage -l; usermod -aG (the missing -a wipes groups) → users.md
TLS, tokens, or replication fail with no config changetimedatectl — an unsynchronized clock breaks certificate and expiry checks → scheduling.md
Logs are missing, or gone after a rebootJournal not persistent, or journald rate-limiting → logs.md
A command behaves differently than documentedDistribution differences: package, unit name, firewall, MAC → distros.md
Copy or sync duplicated a level, or deleted the wrong treersync trailing slash; --dry-run before --deletefiles.md
Host is internet-facing and unreviewedBaseline in order: firewall, key-only SSH, auto security updates → hardening.md
Anything elseCore Rules below, then the file whose name matches the subsystem

Depth on demand: permissions.md denial layers, ACLs, SELinux/AppArmor, capabilities · processes.md signals, D state, limits, /proc · disk-space.md full-disk triage and safe reclaim · storage.md devices, LVM, filesystems, fstab, RAID · memory.md OOM, swap, PSS, cgroup limits · networking.md reachability, DNS, firewalls, MTU, conntrack · ssh.md access, keys, lockout-proof changes · systemd.md units, ordering, drop-ins, sandboxing · scheduling.md cron, timers, locking, clock · boot.md boot failures, GRUB, rescue, chroot · users.md accounts, groups, sudo, PAM, offboarding · packages.md upgrades, holds, broken states, reboots · performance.md saturation triage, PSI, iostat, throttling · logs.md journalctl, rotation, retention · kernel.md sysctl, modules, dmesg, tunables · hardening.md exposed-host baseline · distros.md Debian/RHEL/Arch/Alpine/WSL differences · files.md rsync, find, archives, atomic replace · commands.md incident toolkit.

Core Rules

  1. Never chmod 777. It destroys the audit trail and usually still fails, because the denial is a different layer: ACL mask, SELinux label, mount option, or unit sandboxing. Diagnose with namei -l <path> — it prints every component, and the first failing one is the bug. Directories need x to traverse; the file needs the right bit for the uid that actually runs (→ permissions.md).
  2. Signal ladder: SIGTERM → wait → SIGKILL only if ignored. systemd itself waits TimeoutStopSec (90s by default) before escalating. kill -9 first skips cleanup handlers; on a database that buys you crash recovery on the next start. Nothing at all works on a D-state task (→ processes.md).
  3. Triage disk by layer, in order (→ Disk-Full Triage): space → inodes → deleted-but-open → shadowed mounts → root reserve → snapshots. Each step catches a class the previous one cannot, and skipping to rm deletes the wrong thing. Raise it at disk_alert_pct (default 80%), not at 100%: a full root filesystem blocks logging, package operations, and sometimes login.
  4. Remote-change safety, every time. Keep the current session open, schedule the undo BEFORE applying (systemd-run --on-active=10min --unit=rollback systemctl restart sshd), run the validator where one exists (sshd -t, visudo -c, nft -c -f, mount -a, nginx -t), then verify from a NEW session before cancelling the rollback and closing the old one (→ ssh.md).
  5. Live change ≠ persistent change. Pair every runtime command with its persistence mechanism: sysctl -w with a file in /etc/sysctl.d/, iptables with iptables-save, firewall-cmd with --permanent, systemctl start with enable. "Works now" is untested until it survives a reboot — and the reboot that tests it should be one you chose.
  6. Capacity is relative, not absolute. Alarm on load1 / nproc above load_alarm_ratio (default 1.0) sustained — load 8 on 4 cores is a ratio of 2.0, twice oversubscribed; load 8 on 16 cores is a half-idle host. Alarm on low available in free, never on low "free": cache is doing its job (→ performance.md, memory.md).
  7. Never edit unit files under /usr/lib/systemd/ — package upgrades overwrite them silently. systemctl edit <unit> writes a drop-in under /etc/ that survives and reloads for you; any hand edit needs systemctl daemon-reload or restart runs the old definition (→ systemd.md).
  8. Root is not omnipotent. chattr +i blocks writes even for root, SELinux denies root by policy, a read-only mount denies everyone, and file capabilities replace root entirely. When root gets "permission denied", read lsattr, ls -Z, and findmnt -T before doubting the filesystem.
  9. Guard destructive paths against empty variables and wide matches. rm -rf "${DIR:?}/" aborts when DIR is unset — rm -rf $DIR/ with an unset variable expands to rm -rf /. Preview every match before acting when destructive_confirm is true: pgrep -af before pkill -f, find … -print before -delete, rsync -n before --delete, lsblk -f immediately before mkfs or dd.

Signals And Exit Codes

Formula: an exit status above 128 means killed by signal status − 128. A process can also return those numbers itself, so confirm a real kill in dmesg -T or the journal before blaming the kernel.

StatusMeaningFirst move
1Generic application errorRead the application log, not the OS
126Found but not executablechmod +x, a noexec mount, or a directory where a binary was expected
127Command not foundPATH (cron and units get a minimal one), or a missing shared library — check ldd
130SIGINT (128+2)Ctrl-C, or a parent forwarding it
137SIGKILL (128+9)OOM killer first (dmesg -T | grep -i oom), then a stop-timeout escalation
139SIGSEGV (128+11)Native crash — coredumpctl, and suspect a library or architecture mismatch
141SIGPIPE (128+13)The reader of a pipe exited first (head closing early is the usual cause)
143SIGTERM (128+15)Clean external stop — usually systemd stopping the unit, not a bug
255Wrapper failure (ssh and some runtimes)The transport failed; the remote command may never have run

Disk-Full Triage

Run in this order and stop at the first one that explains the gap. Detail and the safe reclaim order live in disk-space.md.

#CheckCatches
1df -hTWhich filesystem is actually full — the error names a path, not a device
2df -iInode exhaustion: "No space left on device" with free space showing
3lsof +L1Deleted files still held open — rm freed nothing
4mount --bind / /mnt && du -xh --max-depth=1 /mntFiles shadowed under a mount point, invisible to every du
5tune2fs -l <dev> | grep -i 'reserved block'The ext4 5% root reserve — 50 GB on a 1 TB volume, "full" for users
6lvs -o +snap_percent, zfs list -t snapshot, cloud snapshotsDeleted data kept alive by a snapshot
elsedu -xh --max-depth=1 / | sort -h | tailOrdinary growth — descend into the winner

Commands That Lie

ToolWhat it seems to sayWhat is true
free "free" columnMemory is nearly goneCache is reclaimable; only available answers "can I start something"
Load averageThe CPU is overloadedIt counts D-state tasks too — high load with idle CPU is a storage incident
top %CPUAbove 100% is a bugIt is per-core: 400% = four cores saturated
iostat %utilThe disk is maxed outMeaningless on SSD/NVMe with parallel queues — judge by await and queue depth
df vs duOne of them is wrongBoth are right: deleted-but-open files or shadowed mounts explain the gap
ps aux %MEMThese workers use 40 GBShared pages are counted once per process — use PSS (smaps_rollup, smem)
whichThis is what runsIt misses aliases, functions, and builtins — type -a <cmd>
pingThe service is upIt proves ICMP only — nc -zv host port or call the service
digThe name resolvesApplications resolve through NSS, which dig bypasses — getent hosts
df on a thin volumeHalf the disk is freeThin-provisioned and overlay storage can exhaust underneath the filesystem
du -sh *This is the directory totalIt skips dotfiles — du -sh .
uptime 400 daysThe host is reliableIt has never proven it can boot; reboot on a schedule you choose

Output Gates

Before running a destructive or remote-risky command, verify:

  • Variables in destructive paths expanded and echoed first — rm -rf targets use "${VAR:?}"?
  • Fallback session open and a rollback scheduled before touching sshd, sudoers, firewall, fstab, or network config on a remote host?
  • SIGTERM sent and waited before any -9?
  • Persistence step included, or is this change gone at the next reboot?
  • Blast radius previewed — pgrep -af before pkill -f, find … -print before -delete, rsync -n before --delete, lsblk -f before mkfs/dd?
  • Command matches the host's distro_family — package manager, unit name, firewall front end, MAC system?
  • Validator run where one exists (sshd -t, visudo -c, mount -a, nft -c -f, systemd-analyze verify)?

Configuration

User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/linux/config.yaml.

VariableTypeDefaultEffect
distro_familydebian | rhel | arch | alpine | susedebianSelects package manager, unit names, config paths, firewall front end, and MAC system in every command emitted (→ distros.md)
init_systemsystemd | openrc | sysvinitsystemdRoutes service and boot guidance; non-systemd hosts skip systemd.md and timers in favour of the distro's init and cron
firewall_toolauto | ufw | firewalld | nftables | iptablesautoWhich syntax firewall examples use; auto derives it from distro_family (ufw on debian, firewalld on rhel)
privilege_modesudo | root-shellsudoWhether emitted commands carry a sudo prefix and whether sudo-specific traps (secure_path, sudoers.d naming) are surfaced
disk_alert_pctnumber (50-95)80Filesystem usage at which disk triage is raised proactively rather than on request (rule 3)
load_alarm_rationumber (0.5-4)1.0load1 / nproc ratio treated as saturation in capacity judgements (rule 6, performance.md)
destructive_confirmbooltrueWhether every destructive command is preceded by its preview or dry-run (rule 9, Output Gates)
reboot_policyallowed | maintenance-window | nevermaintenance-windowWhether a required reboot is proposed inline, deferred to a window, or reported as a standing requirement (packages.md, kernel.md)

Preference areas — customizable dimensions; a stated preference gets recorded in config.yaml and applied:

  • Tooling: editor, terminal multiplexer for long remote operations, monitoring stack, whether config management (Ansible, Puppet, Salt) owns /etc — affects whether fixes are proposed as commands or as managed configuration
  • Conventions: where local units, scripts, and logs live; naming of hosts, volumes, and users — affects every path in examples
  • Platform: cloud provider, bare metal, VM, WSL, container; architecture; filesystem in use — affects storage, boot, and performance guidance
  • Safety posture: dry-run everything vs act directly, change windows, backup or snapshot required before storage and fstab work — affects how much of a change is proposed before anything runs
  • Output format: one-liners vs explained procedures, command blocks vs prose, how much diagnosis to show alongside the fix
  • Work order: diagnose-then-fix vs fix-then-explain, and whether a review gate exists before production changes
  • Integrations: log destination, alerting target, patch tooling, secret store — the choice, never the credentials
  • Restrictions: compliance regime in force (CIS, STIG), forbidden tools or commands, air-gapped hosts with no package repository access
  • Cadence: patch window, journal and log vacuum schedule, reboot drill frequency

Traps

TrapWhy it failsDo instead
sudo echo x > /etc/fileThe redirect runs in YOUR shell before sudo startsecho x | sudo tee /etc/file
Editing sudoers or fstab bareOne typo = no sudo at all, or an unbootable hostvisudo; edit fstab then mount -a to test before rebooting
Fixing web permissions with chmod -R 777Masks the real cause (ACL mask, SELinux, wrong owner) and makes every file writable by any local processDiagnose: namei -l, getfacl, ls -Z
Testing a cron job in your login shellYour shell has PATH and environment that cron lacks — it proves nothingenv -i /bin/sh -c 'cmd'
Restarting a unit you edited by hand without daemon-reloadsystemd runs the cached definition; you debug a file the system is not usingsystemctl daemon-reload, or use systemctl edit
usermod -G docker aliceWithout -a it REPLACES every supplementary group, including sudousermod -aG, and diff id alice before and after
passwd -l alice as offboardingLocks the password only; her SSH key still logs inusermod --expiredate 1, then remove her authorized_keys
iptables -F to "start clean" on a remote hostWith a DROP default policy you flush your own accessSet policies to ACCEPT first, or schedule a rollback (rule 4)
setenforce 0 to fix a denialHides the label bug and comes back at rebootrestorecon, a boolean, or semanage fcontext (→ permissions.md)
Killing a stuck dpkg/dnf to release the lockLeaves packages half-configured — minutes of waiting become hours of repairFind the holder with fuser -v /var/lib/dpkg/lock-frontend and wait
Storing state in /tmpCleared at reboot, and often tmpfs, so it consumes RAM/var/tmp for temp data that must survive a reboot
dd or mkfs on a device name from memorysda vs sdb is one keystroke, and device names reorder across bootslsblk -f immediately before; address disks by UUID or /dev/disk/by-id

Where Experts Disagree

  • Swap on servers. One camp runs swapless so failures are fast and obvious; the other keeps a few gigabytes so cold pages leave RAM before the OOM killer picks a victim. The boundary is the workload's tolerance for slow degradation: latency-critical services prefer a clean kill, batch and memory-spiky workloads prefer the valve. Both camps agree that sizing swap to match RAM on a server buys nothing.
  • SELinux enforcing vs disabled. "Disable it, it breaks everything" is a real position with a real cost: it removes the only layer that contains a compromised service. The workable middle is enforcing plus the discipline to fix labels and booleans rather than reaching for setenforce 0 — and permissive mode while learning a new application, never as a destination.
  • Host firewall when there is a cloud security group. Redundant to some, defence in depth to others. Deciding factor: whether anything on the host can publish a port without touching the security group (container runtimes do exactly that). Where that is possible, run both.
  • Configuration management vs hand edits. Managed hosts get consistency and lose the ability to fix one box quickly; hand-managed hosts get speed and drift. The rule that satisfies both: any change you would be unhappy to lose at the next converge belongs in the managed configuration, and the incident fix is followed by that commit.

Related Skills

More Clawic skills, get them at https://clawic.com/skills/linux (install if the user confirms):

  • bash — shell scripting syntax and safety, beyond OS behavior
  • sysadmin — routine server administration: users, storage, maintenance
  • docker — container builds, images, and runtime debugging
  • debugging — general fault-isolation strategy when the bug isn't OS-level
  • vps — provisioning and securing rented servers end to end

Feedback

Part of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/linux.

Related skills

bash

@ivangdavila

Writes, debugs, and hardens Bash shell scripts — quoting, arrays, strict mode, traps, argument parsing, and macOS/Linux portability. Use when writing or reviewing any script, one-liner, cron job, deploy script, container entrypoint, or CI step; when a script breaks on spaces in filenames, exits silently, ignores set -e, hangs, or returns the wrong exit code; when quoting, IFS, globs, arrays, heredocs, process substitution, trap, getopts, or mapfile misbehave; when shellcheck flags SC2086 and friends; when unbound variable, bad substitution, command not found, ambiguous redirect, or unexpected end of file show up; when a script works by hand but fails under cron, systemd, sudo, or a CI runner; or when porting between macOS bash 3.2, GNU/Linux, WSL, and POSIX sh. Not for interactive zsh or fish configuration, not for PowerShell, and not for host-level cron, systemd, or permission failures (linux).

34.4k

Server

@ivangdavila

Runs web and application services on a host: process supervision, ports and sockets, worker sizing, restarting without dropping requests. Use when a service will not start, dies after logout, restart-loops, or vanishes on reboot; when a port is already in use, or it answers on localhost but refuses connections from outside; when a request returns 502, 504, 413, 499, or a redirect loop between proxy and app; when wiring nginx, Caddy, Traefik, HAProxy, systemd, PM2, or php-fpm; when sizing workers and threads, tuning keepalive and timeouts, or a box runs out of file descriptors or memory under load; when shipping a release and rolling it back; when serving large uploads, WebSockets, or gRPC through a proxy; and when self-hosting apps or media servers. Not for nginx directive tuning (`nginx`), certificate issuance (`ssl`), host OS failures (`linux`), image building (`docker`), Kubernetes (`k8s`), CI/CD pipelines (`deploy`), or provisioning the machine (`vps`).

22.2k

Kubernetes

@ivangdavila

Debugs Kubernetes workloads and reviews manifests: pods, probes, resources, rollouts, Services, storage, RBAC. Use when a pod is Pending, CrashLoopBackOff, ImagePullBackOff, OOMKilled or stuck Terminating, when a Service or Ingress serves nothing or returns 502/503/504, when cluster DNS is flaky, a rollout hangs or silently ships a broken version, an HPA refuses to scale, a PVC stays unbound, a node goes NotReady or a drain never finishes, when writing or reviewing YAML, Helm charts or kustomize overlays, when tuning requests, limits, QoS, probes and graceful shutdown, or when locking down RBAC, NetworkPolicy, Pod Security and Secrets. Covers kubectl triage, StatefulSets and PVCs, Jobs and CronJobs, autoscaling, admission webhooks, node drains and cluster upgrades. Not for building container images — that is `docker`.

44.5k

terraform

@ivangdavila

Writes, debugs, and refactors Terraform — HCL and modules, plan and apply failures, state surgery, drift, and provider pinning. Use when writing or reviewing HCL, when terraform plan or apply errors out, when a plan shows a permanent diff, an unexplained destroy, or "forces replacement", when renaming, importing, or moving resources between modules and states, when a state lock is stuck or state is lost or corrupted, when for_each fails because a value is not known until apply, when pinning providers or surviving a major version upgrade, or when wiring plan-on-PR, apply-on-merge, drift detection, and policy gates. Covers OpenTofu, tfstate, backends, workspaces, and infrastructure-as-code review. Not for choosing which cloud services to build — see aws, gcp, or azure.

33.6k

Go

@ivangdavila

Writes, debugs, and reviews Go: goroutine leaks, data races, nil interfaces, slices and maps, go.mod, and net/http. Use when Go panics or misbehaves — "all goroutines are asleep - deadlock!", "concurrent map writes", "send on closed channel", "assignment to entry in nil map", or a WARNING: DATA RACE from `go test -race`; when a goroutine, timer, or connection leaks and memory climbs; when a service hangs for want of a timeout; when errors.Is or errors.As stops matching after a %v wrap; when a slice mutates its parent, a map iterates in a new order, or a typed nil returned as error compares non-nil; when go.mod, go.sum, workspaces, replace, or a /v2 path fight over dependencies; when cross-compiling, cgo, build tags, go:embed, or a static container binary fail; when tests are flaky under -race or -parallel or benchmarks lie; when GOGC, GOMEMLIMIT, GOMAXPROCS, or escape analysis need tuning; or when writing handlers, CLIs, database access, or generics. Not for Kubernetes operators or framework internals.

32.5k

nginx

@ivangdavila

Configures and debugs nginx: reverse proxy, load balancing, SSL/TLS termination, caching, redirects, and static file serving. Use when writing or reviewing nginx.conf, server blocks, locations, upstreams, or proxy_pass, when a site behind nginx throws 502, 504, 413, 403, or a redirect loop, when WebSockets, SSE, or gRPC break through the proxy, when a certificate works in curl but warns in browsers, when requests hit the wrong location or the backend sees the wrong path, when tuning workers, buffers, gzip, or proxy cache, when rate limiting or blocking abuse, when proxying raw TCP/UDP, or when nginx runs in Docker or Kubernetes. Not for certificate issuance or renewal (ACME, Let's Encrypt) — that is the ssl skill.

53.9k