Linux Lesson 27 of 47

Systematic Linux Troubleshooting: A Repeatable Method for Boot, Disk, Network, Service & Permission Failures

The difference between a junior operator and a senior one is almost never how many commands they have memorised. It is method. Put both in front of a production box that “won’t come up,” and the junior starts typing commands — restarting things, editing configs, rebooting “to see if it helps” — while the senior sits still for ten seconds, asks what exactly is broken and since when, runs five commands that rule out four whole categories of cause, and then fixes the one thing that was actually wrong. The senior looks slower for the first minute and finishes twenty minutes sooner.

This lesson teaches that method as a mechanical, repeatable loop, and then hands you five playbooks — one each for the failure classes you will meet again and again: won’t boot, disk full or read-only, no network, a service that won’t start, and permission denied. Every playbook is built the same way: a symptom → layer → tool → fix table you can run under pressure, backed by prose on the traps. By the end you should be able to walk up to a broken Linux host you have never seen and diagnose it without guessing — and because the method never changes, every failure you work makes the next one faster.


Why this matters

Everything you have learned so far in this course — boot, storage, networking, systemd, permissions — was construction knowledge: how the system is built. Troubleshooting is the inverse skill: taking a system that is misbehaving and working backwards to the one broken component. Same facts, opposite posture. Construction says “here is how it should be”; diagnosis asks “here is how it is — where does that diverge, and why?”

Beginners struggle because a broken system hands you a symptom, and symptoms are liars. “The website is down” could be DNS, a full disk, a crashed service, a firewall rule, a bad deploy, an expired certificate, or a kernel panic three layers below any of those. React to the symptom — “site’s down, restart the web server” — and you fix it by luck perhaps one time in five; the other four you make it worse or paper over the real cause so it returns at 3am. The symptom tells you where it hurts, not what is wrong. The whole job is turning a symptom into a cause.

The good news: Linux is extraordinarily diagnosable. Almost every subsystem writes its story to a log, exposes its state through /proc or a systemctl/ip/ss query, and fails with a specific error you can look up. It will tell you what is wrong if you ask the right five questions in the right order — which is all the method really is. One more framing that saves years: most outages are self-inflicted and recent, so before cosmic rays and kernel bugs, ask “what changed?” — a deploy, an edit, a disk that filled overnight, an expired cert, a new fstab line. The layer method finds the where; “what changed?” finds the why.


The diagnostic loop: a repeatable method

Here is the entire method as a loop. You will run it for a dead boot, a full disk, or a flaky service — the steps never change, only the commands you plug into them.

# Step What you actually do The trap it prevents
1 Define the problem Write the exact symptom and error text. “It’s broken” is not a problem statement; “systemctl start nginx returns code=exited status=203/EXEC” is. Fixing the wrong thing because you never pinned down what is wrong.
2 Reproduce Make the failure happen on demand. If it only happened once, you cannot prove you fixed it. “It works now” — but you never made it fail, so you have no proof.
3 Establish blast radius One host or the fleet? One user or everyone? One command or all commands? Since boot or since a change? Debugging a host problem that is actually a network/DNS/upstream problem.
4 Isolate the layer Walk the stack hardware → kernel → storage → network → service → app → user. Find the first layer that is provably wrong. Tuning the app while the disk is full, or debugging DNS while the interface is down.
5 Form ONE hypothesis State a single testable cause: “the default route is missing,” not “networking is weird.” Changing five things at once and never learning which mattered.
6 Change ONE thing Make exactly one change, ideally reversible, with the original backed up. A “fix” that introduces a second bug you now cannot separate from the first.
7 Verify Re-run the exact command that was failing and watch it pass. Declaring victory on a silent return code instead of proof.
8 Document + roll forward Note symptom, cause, change, and rollback. Remove temporary hacks (setenforce 0, opened firewall, mounted rescue). The same outage next month with no record, and a box left in a debug state.

The heart of it is step 4 — isolate the layer. Linux is a stack, and a failure at a low layer masquerades as a failure at every layer above it. If the disk is full, the service looks broken, the app looks broken, and the user sees an error — but the cause is storage. The discipline is to walk the stack from the bottom and stop at the first layer that is provably wrong, because everything above it is a symptom, not a cause.

Layer What lives here First question First command
Hardware CPU, RAM, disks, NICs, power Is the hardware healthy? dmesg -l err,crit, smartctl -a /dev/sda
Kernel Drivers, modules, panics, OOM Did the kernel log an error? journalctl -k -b, dmesg -T
Storage Partitions, filesystems, mounts Is there space, and is it writable? df -h, df -i, mount
Network Interfaces, routes, DNS, firewall Can packets get out and names resolve? ip a, ip r, dig, ss -tlnp
Service systemd units / daemons Is the unit running and enabled? systemctl status, journalctl -u
Application The program’s own config/logic Does its own config test pass? nginx -t, app logs
User Permissions, quotas, environment Is it this user / this context? id, sudo -l, namei -l

Walk this table top-down as dmesg → journalctl -k → df → ip → systemctl → app config → id, and the first line that shows a real error is where you stop and dig in. The diagram below renders the whole loop — symptom, triage, the layer isolation, confirmation, and fix — as a single left-to-right map you can hold in your head.

Left-to-right Linux troubleshooting triage map in five stages: a SYMPTOM stage where you define and reproduce the failure and measure its blast radius (one host or the whole fleet); a TRIAGE stage that runs the first-five commands (systemctl --failed, journalctl -p err, df -h, free -h, dmesg) and begins isolating the layer from hardware up to user; a WHICH LAYER decision stage branching into Boot or Service (journalctl -b, systemctl status), Network (ip a, ip r, ss), and Disk or Permission (df -h, ls -Z), where the first provably-wrong layer is the culprit; a CONFIRM stage that reads the log to prove one hypothesis before touching anything; and a FIX and VERIFY stage that changes exactly one thing, re-runs the failing check, and documents the outcome, with numbered badges marking reproduce-first, the first-five commands, isolate-the-layer, confirm-before-fix, one-change-at-a-time, and document-with-a-rollback

Three badges on that map are worth tattooing on your forearm. One change at a time (badge 5): if you edit the config and restart and open the firewall in one go and it works, you have learned nothing and cannot repeat it. Confirm before you fix (badge 4): turn “I think it’s DNS” into “dig fails but ping 8.8.8.8 works, so it is DNS” before you edit resolv.conf. Document with a rollback (badge 6): back up every file before you touch it, and keep a second root session open so a bad sshd, firewall, or fstab change cannot lock you out of the box you are fixing.


Always start here: the 60-second triage

Before any playbook, before any theory, run the same handful of commands every single time. They take under a minute, they need nothing but a shell, and they immediately tell you which layer is on fire. This is your first-five (six, really — the sixth is free):

# 1. Any systemd units in a failed state? (the single most useful command)
systemctl --failed
# 2. Everything at error priority or worse, this boot
journalctl -p err -b --no-pager | tail -n 40
# 3. Any filesystem full? (the #1 cause of "random" breakage)
df -h
# 4. Out of memory / swap? Is something being OOM-killed?
free -h
# 5. Did the kernel log a hardware/driver error?
sudo dmesg -l err,crit -T
# (free sixth) Load and uptime — is the box thrashing, and did it just reboot?
uptime

Learn what each one rules in or out, because that is what turns a list of commands into a diagnosis:

Command What it tells you Points at layer Read it as…
systemctl --failed Which units crashed or won’t start Service A named unit here is your prime suspect — go straight to its status.
journalctl -p err -b All error/critical log lines since boot Any The error text usually names the subsystem and file.
journalctl -k -b Kernel ring buffer for this boot Kernel / hardware I/O errors, OOM kills, filesystem “remounting read-only” live here.
df -h Free space per mounted filesystem Storage Any filesystem at 100% (especially /, /var, /tmp) is almost certainly your cause.
df -i Free inodes per filesystem Storage 100% inodes with free bytes = millions of tiny files; df -h alone misses this.
free -h RAM and swap in use Memory Near-zero available + heavy swap = thrashing; check dmesg for Out of memory: Killed.
dmesg -l err,crit Kernel-level errors, timestamped Hardware / kernel Disk I/O error, EXT4-fs error, NIC resets, OOM — the low-layer truth.
uptime Load average + time since boot System Load ≫ core count = overloaded; a tiny uptime means it just rebooted (crash? OOM? watchdog?).
systemctl status Overall system state (running/degraded) Service State: degraded means at least one unit failed — pair with --failed.

If those six commands are clean — no failed units, no errors, disk and memory fine, no kernel complaints, sane load — then your problem is almost certainly at the application or user layer (a config, a permission, a bad request), and you have just eliminated the entire bottom of the stack in sixty seconds. If one of them lights up, you have your layer and you go to the matching playbook below.

A note on privilege: dmesg may be restricted to root on hardened kernels (kernel.dmesg_restrict=1), and journalctl shows the full system journal only to root or members of the systemd-journal/adm group. When in doubt, run triage with sudo.


Playbook 1 — The system won’t boot

Nothing raises the pulse like a box that won’t come up. But “won’t boot” is a sequence with distinct, separable stages — firmware, bootloader, kernel, initramfs, root pivot, systemd — and the fix depends entirely on how far it got. Your first job is to locate the stall in that sequence, because the recovery for “GRUB never appears” is nothing like the recovery for “systemd hangs on a mount.” If you have not internalised the boot stages, the companion lesson on the boot process: firmware, GRUB, initramfs & systemd targets is the map this playbook navigates.

Symptom Likely layer First check Likely cause Fix
No firmware/POST, blank screen Hardware Screen, beeps, PSU Dead hardware, wrong boot device Fix boot order in firmware; check RAM/PSU
Firmware OK but no GRUB menu Bootloader Does a GRUB prompt appear? Clobbered MBR/ESP, wrong disk Boot rescue media → grub2-install / grub-install + regenerate config
GRUB menu appears, kernel won’t load Bootloader/kernel Pick older kernel entry Bad kernel/initramfs after update Boot previous kernel; rebuild initramfs
Kernel panics: Unable to mount root fs Kernel/storage Panic text names root= Wrong root=UUID=, missing storage driver Fix root=; rebuild initramfs with the driver
Boots then hangs “A start job is running for…” Storage Which unit/mount is named Bad /etc/fstab line, absent device Boot with fstab bypassed → fix the line
Drops to emergency mode shell Storage/filesystem journalctl -xb, systemctl --failed Corrupt filesystem, failed mount Repair FS / fix mount, then systemctl default
Boots but no login / lost root password User Reaches login prompt? Forgotten password rd.break (RHEL) or init=/bin/bashpasswd

Locating the stall

The single most valuable habit is to read the journal from the failed boot. If the box eventually gives you any shell — a rescue prompt, emergency mode, or you can boot a previous kernel — these tell the whole story:

# The current boot's log, errors highlighted
journalctl -xb
# Just this boot at error priority
journalctl -p err -b
# The PREVIOUS boot (the one that failed), if you've since rebooted
journalctl -b -1 -p err
# What is systemd still waiting on / what failed
systemctl --failed
systemctl list-jobs          # jobs still running = what's hanging the boot

If the box does not even reach a shell, you edit the boot live at the GRUB menu. Highlight the entry, press e, find the linux line (the one with vmlinuz and root=), append a parameter, and boot with Ctrl-x (or F10). These are the parameters that get you in:

GRUB kernel parameter Effect Use it when
systemd.unit=rescue.target Boot to single-user rescue (root FS mounted, minimal services) You need a working shell with most of the system
systemd.unit=emergency.target Most minimal shell, root FS often read-only, nothing else mounted Rescue itself fails (e.g. a bad /etc/fstab)
rd.break Break in the initramfs, before root pivot; root is at /sysroot RHEL/Fedora root-password reset, broken root FS
init=/bin/bash Kernel runs bash as PID 1 instead of systemd Distro-agnostic last resort; nothing else running
3 Boot to multi-user.target (text, no GUI) A broken display manager blocks a graphical boot
nomodeset Disable kernel mode-setting for GPU Black screen from a bad graphics driver
single / s Legacy single-user request Older/SysV-flavoured systems

⚠️ Editing the GRUB line only affects this one boot — it is non-destructive and forgotten on reboot, which is exactly why it is safe to experiment with. Nothing you type there is written to disk.

The two boot fixes you will actually perform

Fix A — a bad /etc/fstab hangs the boot. This is the most common self-inflicted boot outage: someone adds a mount, fat-fingers the UUID or device, and on next reboot systemd waits 90 seconds for a device that will never appear, then drops to emergency mode. The fix:

# In emergency/rescue shell: root is often mounted read-only, so remount it writable
mount -o remount,rw /
# Comment out or correct the offending line
vi /etc/fstab
# Prove the file is now valid BEFORE rebooting — this is the whole point
mount -a          # should return with no error; an error means fstab is still wrong
# Continue the boot without rebooting
systemctl daemon-reload
systemctl default

⚠️ Never reboot a box with a fstab you have not validated with mount -a. The single reason fstab mistakes are so painful is that they only bite at boot; mount -a lets you find the mistake before the reboot instead of after. A defence worth knowing: adding nofail to a non-critical fstab entry makes the boot continue even if that device is missing, converting a full outage into a missing mount. The fstab syntax and mount options are covered in depth in the disks, partitions, filesystems & fstab lesson.

Fix B — reset a lost root password (the classic RHCSA task). On RHEL 8/9, Rocky, and Fedora, you break into the initramfs and reset from there:

# 1. At GRUB, press 'e', append rd.break to the 'linux' line, Ctrl-x to boot.
# 2. You land in the initramfs; the real root is mounted READ-ONLY at /sysroot.
mount -o remount,rw /sysroot     # make it writable
chroot /sysroot                  # enter the real system
passwd root                      # set the new password
touch /.autorelabel              # ⚠️ CRITICAL on SELinux: relabel /etc/shadow on next boot
exit                             # leave chroot
exit                             # continue boot

The touch /.autorelabel step is the one everybody forgets and the reason the reset “doesn’t work”: without it, SELinux has the wrong context on the shadow file you just edited and login fails. On Debian/Ubuntu the equivalent is booting with init=/bin/bash (or the recovery menu’s root shell), then mount -o remount,rw / and passwd, with no relabel step because Ubuntu uses AppArmor rather than SELinux. The mechanics of accounts and passwd live in the users, groups & permissions lesson.


Playbook 2 — Disk full or read-only root

A full filesystem is the great impersonator. It makes services crash on startup, databases refuse writes, logins fail (no space for a session file), and the shell throw baffling errors — all while the actual problem is four words: no space left on device. And its uglier cousin, a filesystem that has flipped to read-only, means the kernel detected corruption or an I/O error and protected your data by refusing further writes. Both are near the bottom of the stack, so they masquerade as everything above.

Symptom Likely layer First check Likely cause Fix
No space left on device on writes Storage df -h A filesystem at 100% Find + remove biggest offenders; vacuum journal
df shows space free but writes still fail Storage df -i Inodes exhausted (millions of tiny files) Delete the many small files; find with find/du
Disk usage high but du can’t find it Storage lsof +L1 Deleted-but-open file still held by a process Restart the holding process (or truncate via its fd)
Read-only file system on every write Filesystem mount | grep ' / ', dmesg FS flipped RO after I/O error/corruption Check dmesg; fsck/xfs_repair unmounted; remount
/boot full, kernel updates fail Storage df -h /boot Old kernels/initramfs pile up Remove old kernels (dnf/apt autoremove)
Space vanishes overnight Storage du -x, journal size Runaway log or core dumps Rotate/limit logs; cap journald; disable cores
Cannot create temp file / login fails Storage df -h / /tmp /var / or /tmp full Free space in the specific full mount

Finding what ate the disk

df tells you which filesystem is full; du tells you what filled it. The one flag that matters is -x (--one-file-system), which stops du from wandering across mount points and giving you a meaningless total:

# WHICH filesystem is full? (start here, always)
df -h
# Out of inodes instead of bytes? (df -h looks fine but writes fail)
df -i
# WHAT filled the guilty filesystem — biggest top-level dirs, staying on one FS
sudo du -x -h -d1 / 2>/dev/null | sort -rh | head -20
# Drill down into the biggest offender
sudo du -x -h -d1 /var 2>/dev/null | sort -rh | head -20
# The single biggest files anywhere under a path
sudo find /var -xdev -type f -printf '%s\t%p\n' 2>/dev/null | sort -rn | head -20
Tool / flag What it does Why it matters
df -h Human-readable free space per mount Identifies the filesystem, not the directory
df -i Free inodes per mount Catches “space free but can’t write” — inode exhaustion
df -hT Adds the filesystem type column Tells you ext4 vs xfs vs tmpfs — changes your repair tool
du -x Stay on one filesystem Without it, du / counts /proc, /sys, bind mounts — garbage totals
du -h -d1 Human sizes, one level deep The right granularity to descend a tree fast
lsof +L1 Files with link count < 1 Finds deleted-but-open files — the “phantom” usage
ncdu Interactive disk-usage browser The friendliest way to hunt a full disk (install it)

The two traps: phantom space and read-only root

Trap 1 — deleted but still open. You delete a 40 GB log file, but df still shows the disk full and du cannot find the 40 GB anywhere. The reason: a process still has the file open. On Linux, disk space is not reclaimed until the last file descriptor to a file is closed — deleting the name (unlink) is not enough while a process holds it open. lsof +L1 lists exactly these files (link count zero, still open):

# Find deleted-but-still-open files eating space
sudo lsof +L1
# Example row: the '0' NLINK column and '(deleted)' name are the tell
# COMMAND  PID USER  FD  TYPE  DEVICE  SIZE/OFF NLINK  NODE NAME
# rsyslogd 812 root   7w  REG   253,0 4.0G       0  1234 /var/log/huge.log (deleted)

The fix is to make the process release the descriptor: restart it (systemctl restart rsyslog) or, if you cannot, truncate the file through its still-open fd without restarting: : > /proc/812/fd/7. Restarting is almost always cleaner.

Trap 2 — read-only root. When you see Read-only file system on writes to /, the kernel almost certainly remounted it read-only to protect you after detecting corruption or an I/O error. Do not blindly remount it read-write — first find out why:

# What state is / actually in?
mount | grep ' / '           # look for 'ro' in the options
# Why did it go read-only? The kernel logged it:
sudo dmesg -T | grep -iE 'ext4-fs error|xfs|i/o error|remount' | tail

If dmesg shows a hardware I/O error, remounting rw will only corrupt more — the disk is dying; get the data off and replace it. If it is filesystem corruption on a healthy disk, you repair it — and a filesystem can only be repaired while unmounted, which for the root filesystem means from rescue media or the initramfs:

Filesystem Check tool Repair command Notes
ext2/3/4 e2fsck -n (read-only preview) fsck.ext4 -y /dev/sdaN Must be unmounted; -y auto-answers yes
XFS xfs_repair -n (dry run) xfs_repair /dev/sdaN fsck.xfs is a no-op by design — use xfs_repair
Btrfs btrfs check --readonly btrfs check --repair --repair is a last resort; try btrfs scrub first
Any (mounted RO) mount -o remount,rw / Only after you know it’s a clean/transient reason

⚠️ Never run fsck or xfs_repair on a mounted filesystem — it will corrupt data catastrophically. fsck on the live root is why boxes get bricked. Unmount first, or run it from a rescue environment where the target FS is not mounted.

Finally, the space-recovery moves that are almost always safe: vacuum the systemd journal (frequently the biggest single consumer of /var), and clear old kernels off a full /boot:

# Cap the journal to a size or age — reclaims /var immediately
sudo journalctl --vacuum-size=200M      # keep at most 200 MB
sudo journalctl --vacuum-time=7d        # keep only the last 7 days
# Remove old kernels/packages (Debian/Ubuntu)
sudo apt autoremove --purge
# Remove old kernels (RHEL/Fedora/Rocky) — keep the running one
sudo dnf remove --oldinstallonly        # or: dnf autoremove

For a durable fix (rotating logs, capping journal size permanently), see the logging: journald, rsyslog & logrotate lesson — a full disk is very often a logging problem wearing a storage costume.


Playbook 3 — No network

“No network” is where the layer method pays off most obviously, because networking is itself a stack, and you diagnose it by climbing that stack one rung at a time. The mistake beginners make is jumping straight to the top (“the website won’t load, it must be DNS”) when the interface is down three rungs below. The cure is a fixed ladder you climb in order; the first rung that fails is your layer, and you stop there. The underlying tools — ip, nmcli, ss, dig — are covered in depth in the networking fundamentals lesson; here we weaponise them into a diagnosis.

Symptom Likely layer First check Likely cause Fix
No IP address on the interface Link/L2 ip a Interface down, no DHCP lease, bad cable/VLAN ip link set up; renew DHCP; check nmcli
Have IP, can’t reach anything off-subnet Routing/L3 ip r Missing/wrong default route Add default route; fix nmcli/netplan gateway
Can ping gateway, not the internet Routing/upstream ping 8.8.8.8 Upstream/NAT/route problem beyond you Escalate to network; check firewall/NAT
Can ping 8.8.8.8, names don’t resolve DNS dig, resolvectl status Broken /etc/resolv.conf or DNS server Fix resolver config; test with dig @1.1.1.1
Names resolve, service refuses connection Service/firewall ss -tlnp, curl -v Service not listening, or firewall drop Start/bind the service; open the port
Connection refused on a local port Service ss -tlnp | grep :PORT Nothing is listening on that port Start the service; check its bind address
Connection timed out (not refused) Firewall nft list ruleset, remote FW A firewall silently drops the packet Allow the port on host/security-group firewall

The connectivity ladder

Climb these rungs in order. Each one isolates exactly one layer, and the first failure is your answer:

# Rung 1 — L2/L3 local: is the interface up and does it have an IP?
ip -brief a          # eth0  UP  192.168.1.50/24   <- want an address + UP
ip link              # look for 'state UP' and 'NO-CARRIER' (cable/link problem)

# Rung 2 — routing: is there a default route, and via the right gateway?
ip r                 # want a line: 'default via 192.168.1.1 dev eth0'

# Rung 3 — local reachability: can I reach my own gateway? (tests L2 + local L3)
ping -c3 192.168.1.1

# Rung 4 — internet by IP: does routing/NAT work? (bypasses DNS entirely)
ping -c3 8.8.8.8

# Rung 5 — DNS: can I resolve a name? (only meaningful if rung 4 worked)
dig +short example.com      # or: getent hosts example.com
resolvectl status           # systemd-resolved: which server, which domain

# Rung 6 — the service itself: is the target port actually open and answering?
curl -sv http://example.com:80 -o /dev/null   # full request, verbose handshake
nc -vz example.com 443                          # just: is the TCP port open?
ss -tlnp                                        # locally: what is listening, and as who?
Rung Command Passes → rules out Fails → your layer is
1 ip a Interface + addressing Link / DHCP / driver
2 ip r Local routing table Missing default route
3 ping gateway L2 + local subnet Local network / ARP / VLAN
4 ping 8.8.8.8 Routing + NAT to internet Upstream routing / firewall
5 dig / getent Everything below DNS DNS only
6 curl / ss The whole path The service or its firewall

The elegance is diagnostic subtraction. If ping 8.8.8.8 works but dig fails, you have proven it is DNS — routing and connectivity are fine, only name resolution is broken, so you look at /etc/resolv.conf, resolvectl status, or the DNS server itself. If ping 8.8.8.8 fails but ping <gateway> works, the problem is upstream of you — routing or NAT beyond your host — and no amount of fiddling with resolv.conf will help. Each rung eliminates everything below it.

Refused vs timed out — the tell that saves an hour

The most useful single distinction in network debugging:

# What is listening locally, numeric, with the owning process
ss -tlnp
# LISTEN 0 128 127.0.0.1:5432 ...  <- Postgres only on localhost = remote refused
# LISTEN 0 128       0.0.0.0:80   ...  <- reachable from anywhere

# Host firewall state (pick your distro's front-end)
sudo firewall-cmd --list-all      # RHEL/Fedora (firewalld)
sudo ufw status verbose           # Debian/Ubuntu (ufw)
sudo nft list ruleset             # the modern nftables truth underneath both
sudo iptables -L -n -v            # legacy view (still works via nft compat)

If ss shows the service listening on the right address and you still get a timeout from another host, the drop is a firewall — and in the cloud it is very often the security group, not the OS firewall at all. Check both; the layer method applies to the network too.


Playbook 4 — A service won’t start

A systemd service that refuses to start is the most common day-to-day failure, and systemd is refreshingly honest about why — if you ask it correctly. The two commands that answer 90% of cases are systemctl status -l <unit> (the summary and the last few log lines) and journalctl -u <unit> -e (the full, unfiltered story). Beginners stop at “it failed”; the log almost always contains the exact reason on one line. The unit-file mechanics behind this playbook are covered in the systemd: units, services, targets & journald lesson.

Symptom Likely layer First check Likely cause Fix
status=203/EXEC Service systemctl status -l ExecStart path wrong / not executable Fix ExecStart=; chmod +x the binary
status=200/CHDIR Service systemctl cat WorkingDirectory= doesn’t exist Create the dir or fix the path
Address already in use Service ss -tlnp | grep :PORT Another process owns the port Stop the other process, or change the port
Starts then exits code=exited status=1 Application journalctl -u -e App config error, missing dependency Read the app error; run its config test
Permission denied in the log User/security journalctl, ls -Z, ausearch -m AVC File perms or SELinux denial Fix perms/ownership; restorecon; SELinux boolean
Fails only at boot, fine manually Service Check After=/Requires= Started before a dependency (network/mount) Add After=/Wants= for the real dependency
Job for x.service failed…see 'journalctl -xe' Service journalctl -xe Generic — read the actual error Follow the journal to the specific cause
unit not found Service systemctl daemon-reload New/edited unit not reloaded daemon-reload; check file path + name

The service-debugging sequence

# 1. The summary + last 10 log lines + the exit code (start here)
systemctl status -l nginx.service
# 2. The full journal for this unit, jump to the end
journalctl -u nginx.service -e --no-pager
# 3. See the EFFECTIVE unit file (base + all drop-ins merged)
systemctl cat nginx.service
# 4. Validate the unit file syntax itself
systemd-analyze verify nginx.service
# 5. After ANY unit-file edit — systemd won't see changes until you reload
sudo systemctl daemon-reload

The exit codes in status are a precise vocabulary — learn the common ones and you often skip straight to the fix:

status= code Meaning Usual cause Fix
203/EXEC Couldn’t exec the ExecStart binary Wrong path, not executable, wrong interpreter Verify path with ls -l; chmod +x; fix shebang
200/CHDIR Couldn’t change to WorkingDirectory Directory missing Create it or correct WorkingDirectory=
226/NAMESPACE Sandbox/namespace setup failed ProtectHome=/ReadOnlyPaths= mis-set Fix the hardening directive
1/FAILURE The program itself exited non-zero App-level config/logic error Read the app’s own log line
217/USER The User= doesn’t exist Typo’d or absent service account Create the user or fix User=
code=killed signal=SEGV Crashed (segfault) Bug or corrupt binary/lib Reinstall; check coredumpctl
Result: timeout Didn’t signal ready in time Type=notify service that never notified Fix readiness signalling / raise TimeoutStartSec=

The traps that fool everyone

Config errors — never restart blind. Every serious daemon ships a config test that validates syntax without touching the running service. Run it before you restart, so you never take an outage from a typo:

Service Config test command What it catches
nginx nginx -t Syntax + cert paths, before reload
Apache apachectl configtest / httpd -t Vhost/module syntax
sshd sshd -t ⚠️ Bad sshd_config can lock you out — always test
BIND named-checkconf / named-checkzone Config + zone-file syntax
Postfix postfix check / postconf Main.cf sanity
sudo visudo -c ⚠️ A broken sudoers file breaks all sudo
Any unit systemd-analyze verify unit Unit-file directives

“Address already in use.” The service can’t bind because something already holds its port. Find the squatter and decide who wins:

sudo ss -tlnp | grep ':80 '        # who is on port 80?
# Or by port with lsof:
sudo lsof -i :80

The After= boot-order trap. A service that starts fine when you run it by hand but fails at boot is almost always a dependency ordering problem: it started before the thing it needs (the network was not up, the NFS mount was not ready, the database socket did not exist yet). The fix is to declare the real dependency in the unit — After=network-online.target with Wants=network-online.target, or After= the specific mount/service — and daemon-reload. systemctl list-dependencies <unit> shows the ordering graph.


Playbook 5 — Permission denied

Permission denied (errno EACCES) looks simple and is anything but, because on a modern Linux there are five independent gates a request must pass, and the shell reports all five with the same three words. The skill is knowing which gate slammed. Work them in order — classic Unix rwx first, because it is the most common by far, then the exotic ones. The rwx/ownership model itself is taught in the users, groups & permissions lesson; here we turn it into a diagnosis.

Symptom Likely gate First check Likely cause Fix
Permission denied reading/writing a file DAC (rwx) ls -l, id Wrong mode/owner for your user/group chmod/chown, or add user to the group
Denied, but ls -l mode looks fine ACL getfacl file An ACL entry overrides the visible bits setfacl to grant, or read the ACL mask
Denied on RHEL despite correct rwx SELinux ls -Z, ausearch -m AVC Wrong SELinux context / boolean off restorecon; set the right context/boolean
cd into a dir fails though files readable DAC (dir x) ls -ld dir, namei -l Missing execute on a parent directory chmod +x the traversed directory
sudo: … not allowed sudo policy sudo -l No sudoers rule for you/this command Add a sudoers rule (via visudo)
Permission denied executing a script mount / DAC mount | grep, ls -l noexec mount, or missing x bit Remount without noexec, or chmod +x
Web server 403 on RHEL, files world-readable SELinux ls -Z, journalctl -t setroubleshoot Wrong httpd_sys_content_t context restorecon -Rv, or chcon the content

The five gates, and the tool for each

Gate What it checks Inspect with Fix with
DAC (rwx) Owner/group/other read-write-execute bits ls -l, stat, id chmod, chown, usermod -aG
Path traversal Execute bit on every parent directory namei -l /full/path chmod +x each missing dir
ACL Extended per-user/group grants getfacl setfacl -m, mind the mask
SELinux (MAC) Security context (type enforcement) ls -Z, ausearch -m AVC, sealert restorecon, chcon, setsebool
sudo policy Whether you may run this as root sudo -l visudo rule
Mount options noexec, nosuid, ro on the filesystem mount, findmnt Remount / edit fstab option

Working the gates

Gate 1 — read it literally, and check the path, not just the file. The most missed cause is a missing execute bit on a parent directory: to open /data/reports/q3.csv you need x (search) on /, /data, and /data/reports, plus r on the file. namei -l shows the permission of every component in one shot — it is the single best permission-debugging command and almost nobody knows it:

# Show mode + owner of EVERY component of the path
namei -l /data/reports/q3.csv
# f: /data/reports/q3.csv
# dr-xr-xr-x root root /
# drwxr-x--- alice alice data        <- HERE: 'other' has no x; you're denied traversal
# drwxr-xr-x alice alice reports
# -rw-r--r-- alice alice q3.csv
# Confirm which identity you actually are, including supplementary groups
id

Gate 2 — ACLs hide behind normal-looking bits. If ls -l shows a mode that should grant you access but you are still denied, look for a trailing + on the mode string (-rw-rwx---+) — that plus sign means an ACL is present and overriding your intuition. getfacl reveals it, and watch the mask, which caps every named/group entry:

getfacl /srv/share/file
# user::rw-
# user:bob:rwx        <- bob has an explicit ACL grant...
# mask::r--           <- ...but the mask caps it to r-- ! effective: r--
setfacl -m u:bob:rwx /srv/share/file   # grant, and raise the mask if needed

Gate 3 — SELinux, the RHEL surprise. On RHEL/Fedora/Rocky, SELinux is enforcing by default, and it denies based on context even when rwx is perfect. The tell is that everything looks right but access fails, and a denial (AVC) is logged. The workflow:

# Is SELinux even on? (Permissive/Disabled removes it as a suspect)
getenforce
# The security context of the file (the 4th field: user:role:TYPE:level)
ls -Z /var/www/html/index.html
# The denial record — this NAMES what was blocked and why
sudo ausearch -m AVC -ts recent
# Friendly explanation + suggested fix
sudo sealert -a /var/log/audit/audit.log
# Most common fix: reset the file to its POLICY-CORRECT context
sudo restorecon -Rv /var/www/html

The number-one SELinux cause for beginners: you moved a file into place (with mv, or by extracting a tarball, or serving content from a non-standard path). Moving preserves the old context, so an nginx-served file that came from /home carries a home-directory type that httpd may not read → 403. restorecon relabels it to the correct type; semanage fcontext makes a non-standard path permanent. ⚠️ setenforce 0 (permissive) is a diagnostic — if the problem vanishes, you have proven it is SELinux — but it is not a fix; turn it back on with setenforce 1 and solve the context properly. On Debian/Ubuntu the equivalent MAC layer is AppArmor (aa-status, denials in /var/log/syslog or dmesg, profiles in /etc/apparmor.d/).

Gate 4 — sudo and mount options. sudo -l lists exactly what you are permitted to run as root; if the command is not there, no sudoers rule grants it. And a script that is executable (chmod +x done) but still refuses to run with Permission denied is very often on a filesystem mounted noexec (common for /tmp, /var, and /home on hardened systems) — findmnt -T /path/to/script shows the mount options, and the fix is to run it from an exec-permitted filesystem or remount.


The troubleshooter’s toolbox

Beyond the first-five, a small set of tools covers almost everything else. You do not need to master them all today — you need to know which one answers which question, so that when the standard commands run dry you know where to reach.

Tool Answers the question Example
journalctl “What did the system log, and when?” journalctl -u app -S -1h -p err
systemctl “What is the state of this unit/system?” systemctl status, --failed, is-active
ss “What is listening / connected, and as who?” ss -tlnp, ss -s (summary)
ip “What is the network config?” ip a, ip r, ip -s link
lsof “Who has this file/port/dir open?” lsof /var/log/x, lsof -i :443, lsof +L1
strace “What syscall is this process making/stuck on?” strace -f -p PID
ltrace “What library calls is it making?” ltrace -p PID
/proc/PID/* “What is this exact process doing right now?” cat /proc/PID/status, ls -l /proc/PID/fd
dmesg “What did the kernel just say?” dmesg -T -l err,crit -w
perf top / pidstat “What is burning CPU/IO?” pidstat -d 1, perf top

When a process hangs: strace and /proc

The scariest failure is a process that is simply stuck — not crashed, not logging, just frozen, holding a lock or a port. Here, strace and /proc let you see inside a running process without restarting it. strace prints the system calls a process makes; attach to a hung one and the last line is what it is blocked in:

# Attach to a running (possibly hung) process and watch its syscalls
sudo strace -f -p 1234
# ... if the last line just sits there, that syscall is the block:
#   futex(0x..., FUTEX_WAIT, ...      -> stuck on a lock (deadlock/contention)
#   read(7, ...                        -> waiting on a fd (fd 7 = ? see /proc)
#   connect(5, {sa_family=AF_INET...   -> hung on a network connect (firewall/DNS)
#   stat("/mnt/nfs/...",               -> blocked on a dead NFS/disk mount

# Trace a command from launch, only file-opens, timestamps + durations
strace -ttT -e trace=openat,stat -f myapp 2>strace.out
# A one-line summary of where the time/syscalls went
strace -c -p 1234        # (Ctrl-C to print the table)
strace flag Effect
-p PID Attach to an already-running process
-f Follow child processes (threads/forks) too
-e trace=… Filter to specific syscalls (openat, connect, network, file)
-c Print a summary count/time table instead of every line
-tt / -T Timestamps / time spent in each syscall
-y Show the path/target behind each file descriptor
-o file Write to a file (keeps the terminal readable)

The last syscall printed before the process freezes is the diagnosis — read it against this table:

Last syscall shown The process is blocked on… Where to look next
futex(..., FUTEX_WAIT A userspace lock (deadlock/contention) Other threads: /proc/PID/task/*/stack
read(fd, / recvfrom( Input on a fd that never arrives ls -l /proc/PID/fd/FD — what is that fd?
connect( / poll( on a socket A network peer (firewall drop / dead DNS) The network playbook: ss, ping, dig
stat/openat on a path A dead or hung mount (NFS, failing disk) mount, dmesg; the path’s filesystem
wait4( A child process that never exits The child’s own state (ps, then recurse)
Nothing printed, state D The kernel itself — uninterruptible I/O /proc/PID/wchan, storage/mount health

⚠️ strace pauses the target on every syscall — it can slow a hot process by 10-100× or worsen a timing-sensitive hang. Use it briefly on production, prefer a spare replica, and detach as soon as you have your answer.

When you cannot even attach, /proc/<pid>/ is the process laid bare as files — no tool required, just cat and ls:

Path Shows
/proc/PID/status State (R/S/D/Z), UID/GID, memory (VmRSS), threads
/proc/PID/cmdline The exact command line (NUL-separated)
/proc/PID/cwd Symlink to its working directory
/proc/PID/fd/ Every open file descriptor (files, sockets, pipes)
/proc/PID/environ Its environment (great for “what did cron pass?”)
/proc/PID/limits Its ulimits (open-file caps, memory)
/proc/PID/wchan The kernel function it is sleeping in
/proc/PID/stack Kernel stack (why a D-state process is stuck)

A process in state D (uninterruptible sleep) in /proc/PID/status is blocked in the kernel — almost always on disk or a hung NFS mount — and you cannot kill -9 it out of that state; you fix the storage/mount underneath. That single fact demystifies half of “why won’t this process die?” questions.


Staying safe: change control and blast radius

The fastest way to turn a small incident into a large one is a careless fix. Before you change anything on a system that is already unhappy, spend the ten seconds these habits cost — they are what separate an operator from someone who got lucky.

Habit Why How
Back up before you edit You need a way back from a bad edit cp sshd_config sshd_config.$(date +%F)
Keep a second root session A bad sshd/firewall/fstab change can lock you out Open a second SSH session and leave it logged in
Change ONE thing So you know what fixed (or broke) it One edit → verify → next
Test config before reload Never take an outage from a typo nginx -t, sshd -t, visudo -c, mount -a
Verify with the failing command A silent return is not proof Re-run the exact thing that failed
Undo your debug hacks setenforce 0, opened ports, RO→RW remounts leave the box unsafe Revert every temporary change
Write it down Next month’s you (or a colleague) needs the story Symptom · cause · change · rollback

Two deserve emphasis. Keep a second session is the one people learn the hard way: edit sshd_config or the firewall over SSH, apply it, get disconnected, and discover the change locked out every login — with no console access. A second, already-authenticated session lets you undo it even when the new config refuses connections. Back up before you edit makes every risky change reversible — cp file file.bak costs nothing and has saved more outages than any monitoring tool. That last loop step, document and keep a way back, is not paperwork; it is the safety net that lets you move fast on the next incident.


Hands-on lab

⚠️ Run this on a throwaway VM, WSL instance, or container you can destroy — it deliberately breaks things. Do not run it on anything you care about. Every step cleans up after itself, but a snapshot first is cheap insurance.

We will manufacture four of the five failure classes and diagnose each by method, so the loop becomes muscle memory.

Step 1 — Baseline triage. Learn what “healthy” looks like on this box before you break it.

systemctl --failed          # expect: 0 loaded units listed
df -h                       # note the % on / and /var
free -h ; uptime            # note available memory and load

What just happened: you established the baseline. Every later step compares against this. On a healthy box systemctl --failed shows nothing — so when it shows something later, you know it is your fault.

Step 2 — Break a service, then diagnose it (Playbook 4).

# Create a broken unit: the ExecStart binary does not exist
sudo tee /etc/systemd/system/lab.service >/dev/null <<'EOF'
[Unit]
Description=Lab broken service
[Service]
ExecStart=/usr/local/bin/does-not-exist
EOF
sudo systemctl daemon-reload
sudo systemctl start lab.service        # returns an error
# --- now diagnose, don't peek at the cause above ---
systemctl status -l lab.service         # look for: status=203/EXEC
journalctl -u lab.service -e --no-pager # the exact failing path

What just happened: status=203/EXEC is systemd telling you it could not execute the ExecStart path — Playbook 4’s first row. The fix is to correct the path. You just read a real exit code and mapped it to a cause.

Step 3 — Simulate a full disk, including phantom space (Playbook 2).

# Build a tiny 20 MB throwaway filesystem in a file (no real disk touched)
mkdir -p /tmp/lab && dd if=/dev/zero of=/tmp/lab/disk.img bs=1M count=20 status=none
mkfs.ext4 -q /tmp/lab/disk.img
sudo mkdir -p /mnt/lab && sudo mount -o loop /tmp/lab/disk.img /mnt/lab
df -h /mnt/lab                                   # ~18M, mostly free
# Fill it and watch the write fail:
sudo dd if=/dev/zero of=/mnt/lab/fill bs=1M count=50 2>&1 | tail -1   # No space left
df -h  /mnt/lab                                  # 100% used
# The phantom-space trap: hold a deleted file open in a subshell
sudo bash -c 'exec 9>/mnt/lab/ghost; dd if=/dev/zero of=/mnt/lab/ghost bs=1M count=5 status=none; rm /mnt/lab/ghost; echo "deleted ghost, but df still shows it used:"; df -h /mnt/lab; lsof +L1 /mnt/lab; exec 9>&-'

What just happened: df reported 100% and, after rm, the space was still consumed until the file descriptor closed — exactly the deleted-but-open trap, and lsof +L1 named the culprit. This is the failure that makes people reboot in confusion; you now recognise it in seconds.

Step 4 — Manufacture a permission-denied path (Playbook 5).

sudo mkdir -p /srv/lab/private && echo secret | sudo tee /srv/lab/private/data.txt >/dev/null
sudo chmod 750 /srv/lab/private            # 'other' loses execute/search on the dir
sudo useradd labtester 2>/dev/null || true
sudo -u labtester cat /srv/lab/private/data.txt   # Permission denied
# --- diagnose by path, not guesswork ---
sudo -u labtester namei -l /srv/lab/private/data.txt   # find the exact denying component

What just happened: namei -l walked every directory and showed which one lacked the search (x) bit for labtester — the file itself was readable, but a parent directory blocked traversal. That is the permission trap beginners never find with ls -l on the file alone.

Step 5 — Clean up. Leave no trace.

sudo umount /mnt/lab && sudo rmdir /mnt/lab && rm -rf /tmp/lab
sudo systemctl stop lab.service 2>/dev/null; sudo rm /etc/systemd/system/lab.service; sudo systemctl daemon-reload
sudo rm -rf /srv/lab && sudo userdel labtester 2>/dev/null
systemctl --failed          # back to a clean baseline

What just happened: you restored the baseline from Step 1 — the undo your debug hacks discipline in miniature. Diagnose, then always return the box to a known-good state.


Common mistakes and troubleshooting

Symptom / mistake Cause Fix
“Fixed” it but it breaks again next day Treated the symptom, not the cause (restarted the app while the disk kept filling) Isolate the layer; fix the root cause, then verify it stays fixed
Changed five things, now it works, don’t know why Violated one-change-at-a-time Revert to baseline; change one variable at a time
Edited a unit file, systemctl ignores it systemd caches unit files sudo systemctl daemon-reload after every unit edit
Reset root password on RHEL, still can’t log in Skipped touch /.autorelabel; SELinux context on /etc/shadow wrong Reboot with autorelabel, or restorecon /etc/shadow
df says full, du can’t find the space Deleted-but-open file held by a process lsof +L1; restart the holding process
Ran fsck on the mounted root, corrupted it Filesystem repair on a mounted FS Only ever fsck/xfs_repair unmounted (rescue/initramfs)
Locked out after editing sshd_config Applied an untested SSH change over SSH Always sshd -t first, and keep a second session open
Web 403 on RHEL though files are readable Wrong SELinux context (moved/extracted file) restorecon -Rv; don’t disable SELinux
ping works but nothing loads Stopped climbing the ladder too early Continue: dig (DNS) and ss/curl (service)
Killed a stuck process with -9, won’t die Process in D (uninterruptible) state on dead I/O Fix the storage/mount; D-state ignores signals

The three nastiest, spelled out. First: the symptom-fix loop. You restart the crashing service, close the ticket — and it crashes again in an hour because the disk is still 100% full. The restart “worked” only by buying minutes before the log refilled the disk. If a fix works but you cannot explain the mechanism, you have not fixed anything, you have reset the clock — always be able to answer “why did that fix it?”

Second: the untested reload that locks you out. sshd, firewalld/ufw, and /etc/fstab are the three files that can sever your own connection to the box. Test each before applying — sshd -t, mount -a — and keep a second authenticated session open as your parachute.

Third: fixing the wrong layer. The whole method exists to prevent this, yet it is still the most common error under pressure: the app developer debugs the app, the DBA blames the query, while df -h would have shown a full disk in three seconds. Run the first-five before you form any theory.


Cheat-sheet

Command What it does
systemctl --failed List all failed units (best first command)
journalctl -p err -b All errors this boot
journalctl -xb Full current-boot log, with explanations
journalctl -b -1 The previous (failed) boot’s log
journalctl -u UNIT -e -f A unit’s log, end, follow live
df -h / df -i / df -hT Free space / inodes / with FS type
du -xh -d1 /path | sort -rh Biggest dirs, one filesystem
lsof +L1 Deleted-but-open files eating space
free -h Memory and swap
dmesg -T -l err,crit Kernel errors, human timestamps
uptime Load average + time since boot
ip -br a / ip r Interfaces+IPs (brief) / routing table
ping -c3 8.8.8.8 Internet by IP (bypass DNS)
dig +short NAME / resolvectl status DNS resolution / resolver config
ss -tlnp Listening TCP sockets + owning process
curl -sv URL / nc -vz host port Test a service / test a port open
firewall-cmd --list-all / ufw status Host firewall rules (RHEL / Debian)
systemctl status -l UNIT Unit summary + last log + exit code
systemctl cat UNIT Effective merged unit file
systemd-analyze verify UNIT Validate unit-file syntax
nginx -t / sshd -t / visudo -c Config-syntax tests (before reload!)
ls -l / ls -Z / getfacl rwx+owner / SELinux context / ACLs
namei -l /full/path Perms of every component of a path
id / sudo -l Your identity+groups / your sudo rights
restorecon -Rv PATH / getenforce Fix SELinux labels / SELinux mode
ausearch -m AVC -ts recent Recent SELinux denials
strace -f -p PID Watch a process’s syscalls (find a hang)
cat /proc/PID/status / ls -l /proc/PID/fd Process state / open descriptors
mount -o remount,rw / Remount root read-write (after root cause!)
mount -a Validate /etc/fstab before rebooting

Interview and exam questions

Q: A server is “slow and throwing random errors.” What are the first commands you run and why? A: The first-five triage: systemctl --failed, journalctl -p err -b, df -h (and df -i), free -h, dmesg -l err, plus uptime. They take under a minute and isolate the layer — a failed unit, a full disk/inode table, memory exhaustion, or a kernel/hardware error — before I form any theory. “Random errors” are very often a single full filesystem impersonating many failures.

Q: df -h shows a filesystem 100% full, but du -sh on it accounts for far less. Explain. A: A process is holding a deleted-but-open file. On Linux, space is freed only when the last file descriptor closes, so rm on a large log a daemon still has open removes the name but not the data. du walks names and can’t see it; lsof +L1 lists it (link count 0, (deleted)). Fix: restart the holding process, or truncate via /proc/PID/fd/N.

Q: You can ping 8.8.8.8 but curl https://example.com fails. Where is the problem and how do you prove it? A: Connectivity and routing are fine (ICMP to a public IP works), so the fault is above routing — almost certainly DNS. Prove it with dig +short example.com or getent hosts example.com: if resolution fails while ping 8.8.8.8 works, it is DNS, and I check /etc/resolv.conf / resolvectl status, testing against a known resolver with dig @1.1.1.1 example.com.

Q: A systemd service fails with status=203/EXEC. What does that mean and how do you fix it? A: systemd could not execute the ExecStart binary — the path is wrong, the file isn’t executable, or the interpreter/shebang is bad. Confirm with systemctl status -l and systemctl cat to see the effective ExecStart, then ls -l the path; fix the path or chmod +x, then daemon-reload and start. Verify with systemctl is-active.

Q: (RHCSA) You’ve forgotten the root password on a RHEL 9 box. Walk through resetting it. A: Reboot; at GRUB press e, append rd.break to the linux line, Ctrl-x. In the initramfs, mount -o remount,rw /sysroot, chroot /sysroot, passwd root, then touch /.autorelabel so SELinux relabels /etc/shadow on next boot, exit, exit. Omitting the relabel is why the reset “doesn’t work.”

Q: What is the difference between Connection refused and Connection timed out, diagnostically? A: refused = the packet reached the host and got a TCP RST — the path works, but nothing is listening on that port (or it’s bound to 127.0.0.1 only). Check ss -tlnp. timed out = the packet vanished with no reply — the fingerprint of a firewall dropping it (host firewall or cloud security group). Refused → service problem; timeout → firewall problem.

Q: A file is -rw-r--r-- and owned by you, yet you get Permission denied opening it. Name two causes. A: (1) A parent directory lacks the execute/search bit, so you can’t traverse the path — check with namei -l. (2) An ACL or SELinux context overrides the visible bits — a trailing + on the mode means an ACL (getfacl), and on RHEL a wrong context denies despite correct rwx (ls -Z, ausearch -m AVC).

Q: The boot hangs at “A start job is running for /mnt/data.” What happened and how do you recover? A: A /etc/fstab entry references a device that isn’t present, so systemd waits (default ~90s) then drops to emergency mode. Recover by booting to emergency.target (or the box drops there itself), mount -o remount,rw /, comment/fix the bad fstab line, validate with mount -a, then systemctl default. Prevention: add nofail to non-critical mounts.

Q: How do you investigate a process that is completely hung (not using CPU, not crashing)? A: Attach strace -f -p PID; the last syscall shown is where it’s blocked — futex (lock), read/connect (I/O or network), stat on a dead mount. Cross-reference /proc/PID/status (state D = uninterruptible kernel sleep, usually dead disk/NFS), /proc/PID/wchan, and ls -l /proc/PID/fd to see which fd it’s stuck on. A D-state process ignores kill -9 — fix the underlying I/O.

Q: Your root filesystem is mounted read-only and every write fails. What do you do? A: Don’t blindly remount rw. Check dmesg for why: an I/O error means failing hardware (get data off, replace the disk — remounting rw risks more corruption); filesystem corruption on healthy hardware means repair it unmounted (fsck.ext4 -y or xfs_repair from rescue/initramfs), then remount. Only remount rw once you know the cause is transient/clean.

Q: Why is “change one thing at a time” a hard rule, not just advice? A: Because a fix you can’t attribute is a fix you can’t repeat or safely revert. If I edit config, restart, and open a port together and it works, I’ve learned nothing and may have introduced a new latent bug masked by the fix. One change → verify with the failing command → next. It’s slower for one incident and far faster across a career.

Q: What’s the single most common troubleshooting error, and how does the method prevent it? A: Fixing the wrong layer — debugging the app while the disk is full, or resolv.conf while the interface is down. The layer-isolation step (walk hardware→kernel→storage→network→service→app→user, stop at the first provably-wrong layer) plus running the first-five triage before forming a theory prevents it structurally.


Key takeaways

linuxtroubleshootingrhcsasystemdjournalctlsystemctlfsckselinuxnetworkingpermissionsbootdebuggingstracesysadmin
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments