DevOps Nimbus Linux

Linux errors and how to fix them

89 error messages and symptoms from real servers. Each answer gives the exact message, what it means, every cause that prints it with a check that proves it and the fix for that cause, how to prevent it, and then a scenario to break and fix it yourself in the playground. Generated from the site's data; the interactive reference adds search by pasted error text.

Files and permissions

Files and permissions

Permission denied when running a script

-bash: ./deploy.sh: Permission denied

What it means

The shell asked the kernel to run deploy.sh as a program and the kernel said no. For ./name that needs three things: an execute bit on the file, a mount that allows running programs, and search (x) permission on every directory on the way. You own the file, so ownership is not the problem.

Causes, most likely first

  1. The file has no execute bit (-rw-r--r--). Without x, the kernel refuses to run it as a program, whoever owns it.

    Check

    ls -l deploy.sh   # no x in the mode (-rw-r--r--): this is your cause

    Fix

    chmod u+x deploy.sh (or 755 if others run it too).

  2. The file system is mounted noexec (a hardened /tmp or /home often is). noexec forbids running any program from that mount, even one with the x bit.

    Check

    findmnt -no OPTIONS --target deploy.sh   # noexec in the list: the mount forbids it

    Fix

    Keep scripts where execution is allowed (/usr/local/bin, /opt). bash deploy.sh works, but it sidesteps a policy someone set on purpose.

  3. A directory on the path is not searchable for you. Without x on a directory, nothing inside it can be reached, so the run fails the same way.

    Check

    namei -l "$PWD/deploy.sh"   # a directory without x for you: this is your cause

    Fix

    Add x for the group or others on that directory (chmod o+x DIR), never 777.

Prevent it

Commit scripts with the bit set (git update-index --chmod=+x). Never use 777: it lets anyone rewrite what runs.

Now learn it: break and fix it yourself in Permission denied on a script, then take Level 02 · Permissions and ownership. Open in the interactive reference · Link to this answer

Files and permissions

bad interpreter: No such file or directory

-bash: ./deploy.sh: /bin/bash^M: bad interpreter: No such file or directory

What it means

The script's first line (#!, the shebang) names the program that should run it, and the kernel could not find that program. /bin/bash^M shows the name it looked for: ^M is a carriage return, so it looked for a file called "bash" plus an invisible character.

Causes, most likely first

  1. Windows line endings (CRLF). Each line ends in \r\n, so the shebang names /bin/bash\r, which does not exist.

    Check

    file deploy.sh   # with CRLF line terminators: this is your cause
    head -1 deploy.sh | od -c | head -2   # \r before \n confirms it

    Fix

    sed -i 's/\r$//' deploy.sh, or dos2unix deploy.sh (apt or dnf install dos2unix).

  2. The shebang names an interpreter that is not installed (python, an old path). The kernel reports the missing interpreter with the same words.

    Check

    head -1 deploy.sh   # the interpreter it asks for
    command -v bash python3   # no output for that name: it is not installed

    Fix

    Install it, or point the shebang at one that exists, such as #!/usr/bin/env python3.

Prevent it

Add *.sh text eol=lf to .gitattributes, and set your editor to LF for scripts.

Now learn it: break and fix it yourself in bad interpreter: ^M, then take Level 02 · Permissions and ownership. Open in the interactive reference · Link to this answer

Files and permissions

Operation not permitted, even as root

sed: cannot rename /etc/nginx/conf.d/sedXk3pQa: Operation not permitted

What it means

root tried to change a file and the kernel refused with EPERM, "Operation not permitted". Unlike "Permission denied", this is not about modes: something forbids the operation even for root. Here sed wrote a temporary file and could not rename it over the original.

Causes, most likely first

  1. The file has the immutable attribute (chattr +i). An immutable file cannot be changed, renamed over or deleted, by anyone, until the flag is removed.

    Check

    lsattr /etc/nginx/conf.d/rate-limit.conf   # an i in the flags: this is your cause

    Fix

    sudo chattr -i FILE, edit it, then sudo chattr +i FILE again if the lock was intended.

  2. The append-only attribute (chattr +a). Appends work, but rewriting, renaming or truncating fails with this error.

    Check

    lsattr /etc/nginx/conf.d/rate-limit.conf   # an a in the flags

    Fix

    sudo chattr -a FILE, make the change, and put the flag back if it was intended.

  3. The directory is immutable. Then nothing in it can be created, renamed or removed, so the temporary file or the rename fails.

    Check

    lsattr -d /etc/nginx/conf.d   # an i on the directory itself

    Fix

    sudo chattr -i DIR, make the change, then restore the flag if it was intended.

  4. root without the capability: inside a container, or chown on an NFS share exported with root_squash. That root is not the real root, so the kernel or the server says no.

    Check

    findmnt -no FSTYPE,OPTIONS -T /etc/nginx/conf.d   # nfs, proc or sysfs: the file system refuses
    grep CapEff /proc/self/status   # fewer bits than the same line on the host: a restricted root

    Fix

    Make the change where root really has the right: on the host, or on the NFS server.

Prevent it

Document every immutable file; config management should know about them.

Now learn it: break and fix it yourself in Operation not permitted, even as root, then take Level 02 · Permissions and ownership. Open in the interactive reference · Link to this answer

Files and permissions

EACCES: permission denied in a service's log

Error: EACCES: permission denied, open '/srv/shop/uploads/receipt-8813.pdf'

What it means

The service tried to open a file or folder and the kernel said its user may not. EACCES is the kernel's "Permission denied": the process's user, groups, the file's mode, and on Rocky the SELinux label decided against it. The path in quotes is the one it failed on.

Causes, most likely first

  1. Files created by root with sudo (tar, cp, git pull) in the app's folders. They belong to root, so the service user cannot write them.

    Check

    namei -l /srv/shop/uploads   # root-owned parts on the path
    sudo -u shop test -w /srv/shop/uploads && echo writable   # no output: the service user cannot write

    Fix

    sudo chown -R shop:shop /srv/shop/uploads.

  2. The service runs as a different user than you think. The modes may be right for the user you expect, not for the one actually running.

    Check

    systemctl show -p User shop   # the user it runs as; empty means root
    ps -o user= -p $(pgrep -o -f shop)   # the user of the running process

    Fix

    Give that user access, or set User= in the unit to the account the files belong to.

  3. A secret tightened to root-only that the service reads itself. After chmod 600 the service user can no longer read it.

    Check

    ls -l FILE   # -rw------- root root: only root can read it

    Fix

    sudo chown root:shop FILE && sudo chmod 640 FILE.

  4. On Rocky/RHEL, SELinux denies it. Modes and owners are right, but the file's label does not allow this service's domain.

    Check

    sudo ausearch -m avc -ts recent   # a denial naming the path: this is your cause

    Fix

    Relabel with semanage fcontext and restorecon (see SELinux denial). Never setenforce 0.

  5. systemd sandboxing (ProtectHome=, ProtectSystem=) hides or locks the path for this service only. It works by hand but not in the service.

    Check

    systemctl show -p ProtectHome -p ProtectSystem -p ReadWritePaths shop   # yes or strict, and the path is not in ReadWritePaths

    Fix

    A drop-in with ReadWritePaths=/srv/shop/uploads, then daemon-reload and restart.

Prevent it

Run deploy steps as the service user (sudo -u shop ...), not as root.

Now learn it: break and fix it yourself in Files owned by root after sudo · A world-readable secrets file, then take Level 02 · Permissions and ownership. Open in the interactive reference · Link to this answer

Files and permissions

403 Forbidden from nginx

HTTP/1.1 403 Forbidden

What it means

nginx found the request but refused to serve it, and the browser got 403 Forbidden. The reason is not on the page: nginx writes it to its error log, and the log line tells you which cause you have. Some pages working means the server is fine; the problem is these files.

Causes, most likely first

  1. The file or a directory on its path is not readable by the web user (www-data on Ubuntu, nginx on Rocky). nginx gets "Permission denied" from the kernel and answers 403.

    Check

    sudo tail -n 5 /var/log/nginx/error.log   # (13: Permission denied): modes or SELinux
    namei -l /var/www/html/report.html   # the first part without r (file) or x (directory) for the web user

    Fix

    chmod 644 FILE and 755 for folders.

  2. A restrictive umask on the deploy user. New files are created 600 or 640, so only the newest pages fail.

    Check

    ls -l /var/www/html   # new files without r for others
    umask   # as the deploy user: 0077 or 0027

    Fix

    Set umask 022 in the deploy user's profile or deploy tool, and fix the existing files.

  3. A directory with no index file while autoindex is off. nginx refuses to list the folder and says 403.

    Check

    sudo tail -n 5 /var/log/nginx/error.log   # directory index of ... is forbidden: this is your cause

    Fix

    Add the index file, or fix the index directive.

  4. An allow/deny rule in the config matches the client.

    Check

    sudo tail -n 5 /var/log/nginx/error.log   # access forbidden by rule: this is your cause

    Fix

    Correct the allow/deny lines, then sudo nginx -t && sudo systemctl reload nginx.

  5. On Rocky/RHEL, an SELinux label. Modes are right, but nginx may only read files labelled for the web.

    Check

    sudo ausearch -m avc -ts recent   # a denial for nginx on that file

    Fix

    Relabel (see SELinux denial).

Prevent it

Deploy with a known umask, and test a fresh page after each deploy.

Now learn it: break and fix it yourself in 403 Forbidden after every deploy, then take Level 02 · Permissions and ownership. Open in the interactive reference · Link to this answer

Files and permissions

command not found (but it works for me)

/bin/sh: 1: backup-uploads.sh: not found

What it means

The shell looked for the command in each folder of its PATH and did not find it. /bin/sh: 1: means dash (Ubuntu's sh) ran it, as cron and many scripts do, not your interactive bash. The same name works for you because your shell has a different PATH, or the file is somewhere only you look.

Causes, most likely first

  1. A different PATH. cron uses /usr/bin:/bin; sudo uses secure_path, which on Rocky/RHEL leaves out /usr/local/bin. A tool in /usr/local/bin or your home is found by you, not by them.

    Check

    command -v backup-uploads.sh   # where your shell finds it
    env -i PATH=/usr/bin:/bin /bin/sh -c 'backup-uploads.sh'   # not found here too: this is your cause
    sudo grep secure_path /etc/sudoers   # the PATH sudo uses

    Fix

    Use absolute paths in the job, or set PATH= at the top of the crontab.

  2. The script exists but its #! interpreter does not. dash reports that as "not found" too, which hides the real problem.

    Check

    head -1 "$(command -v backup-uploads.sh)"   # an interpreter that is not installed

    Fix

    Install the interpreter or correct the shebang.

  3. The package is not installed on this machine.

    Check

    command -v backup-uploads.sh   # no output in your shell either

    Fix

    sudo apt install PACKAGE (Ubuntu suggests which); on Rocky, dnf provides '*/NAME' finds it.

Prevent it

Scripts for automation use absolute paths or set PATH themselves.

Now learn it: break and fix it yourself in Cron job that never works, then take Level 08 · Scheduling with cron. Open in the interactive reference · Link to this answer

Files and permissions

Argument list too long

-bash: /usr/bin/rm: Argument list too long

What it means

The shell expanded * into every file name in the folder and tried to pass them all to rm. The kernel limits how much a single command line may hold (getconf ARG_MAX), and this list was larger, so rm never started.

Cause

  1. The glob expands to more arguments than the kernel accepts in one command line. It happens only in folders with a huge number of files.

    Check

    ls /var/lib/php/sessions | wc -l   # hundreds of thousands: this is your cause
    getconf ARG_MAX   # the limit in bytes

    Fix

    Let find do it without a glob: find /var/lib/php/sessions -type f -mmin +1440 -delete, or find DIR -type f -print0 | xargs -0 rm.

Prevent it

Clean up with a scheduled find -delete; do not let one folder collect millions of files.

Now learn it: break and fix it yourself in No space left, with space left, then take Level 09 · Disk space. Open in the interactive reference · Link to this answer

Processes

Processes

High load average, CPU at 100%

load average: 2.02, 1.88, 1.53

What it means

The load average is the number of processes running or waiting to run, averaged over 1, 5 and 15 minutes. Compare it with the number of CPUs (nproc): load above that means work is queueing. On Linux it also counts processes waiting on disk, so high load is not always CPU.

Causes, most likely first

  1. A runaway job or loop uses a whole CPU. One process near 100% in top is the sign.

    Check

    top   # press P: one process at the top near 100%
    ps aux --sort=-%cpu | head

    Fix

    kill PID (SIGTERM) for a stray job, or limit it: sudo systemctl set-property NAME CPUQuota=50%.

  2. Too many workers for the CPUs. Many processes share the CPUs, each modest, but together more than the box has.

    Check

    uptime && nproc   # load well above the CPU count
    vmstat 1 5   # us+sy near 100 and a long r column

    Fix

    Lower the worker count to fit the CPUs, or add CPUs.

  3. Processes waiting on disk, not CPU. Linux counts them in the load too.

    Check

    vmstat 1 5   # wa and b high, us low: see I/O wait

    Fix

    Treat it as disk (see High load, idle CPU).

  4. On a cloud VM, CPU steal or used-up burst credits. The hypervisor gives you less CPU than the VM thinks it has.

    Check

    vmstat 1 5   # st high: the hypervisor takes the CPU

    Fix

    Move to a larger or non-burstable instance type.

Prevent it

Run batch work under limits (systemd-run -p CPUQuota=) or off-peak.

Now learn it: break and fix it yourself in A process eating the CPU, then take Level 05 · Processes and signals. Open in the interactive reference · Link to this answer

Processes

Half-written file or stale lock after kill -9

export.csv.part left behind, batchd.lock still present

What it means

The process was killed with SIGKILL (kill -9). SIGKILL cannot be caught: the program gets no chance to flush buffers, rename its finished file or remove its lock. What it left behind is a half-written file and a lock that now blocks the next start.

Cause

  1. SIGKILL cannot be caught, so no flush, no rename, no cleanup happened. The .part file and the lock are leftovers.

    Check

    ls -l /var/lib/batch   # .part and .lock files left behind
    cat /var/lib/batch/batchd.lock && ps -p $(cat /var/lib/batch/batchd.lock)   # no process: the lock is stale

    Fix

    Remove the stale lock only after checking no process owns it, and rerun the job for a complete file. Next time: kill PID (TERM), wait, and use -9 only if it ignores TERM.

Prevent it

Stop services with systemctl stop, which sends TERM and waits.

Now learn it: break and fix it yourself in kill -9 or a clean stop, then take Level 05 · Processes and signals. Open in the interactive reference · Link to this answer

Processes

Zombie <defunct> processes

[convert] <defunct>

What it means

A zombie is a process that has already exited, and whose exit status its parent has not collected yet (with wait()). It uses no CPU or memory, only a slot in the process table, and kill -9 cannot remove it because it is already dead.

Causes, most likely first

  1. The parent never called wait() for its exited children. Until it does, each child stays as a zombie.

    Check

    ps -eo pid,ppid,stat,cmd | awk '$3 ~ /^Z/'   # the zombies and their parent (PPID)
    ps -fp PPID   # the parent that does not reap

    Fix

    Restart or stop the parent: init then adopts and reaps the zombies. Fix the program to wait() (or ignore SIGCHLD).

  2. In a container, PID 1 is your program. It does not reap orphaned children the way init does, so they pile up.

    Check

    ps -p 1 -o comm=   # your program, not an init

    Fix

    docker run --init, or tini as PID 1.

Prevent it

A few zombies are harmless; thousands exhaust PIDs. Alert on the count.

Now learn it: break and fix it yourself in Zombies that will not die, then take Level 05 · Processes and signals. Open in the interactive reference · Link to this answer

Processes

Already running (stale PID file)

reportd: another instance is running (pid 1187), exiting

What it means

The daemon found its PID file, read a process number from it, saw that a process with that number exists, and refused to start a second copy. After a crash or reboot that number usually belongs to some other program.

Causes, most likely first

  1. The PID file survived a crash or reboot, and its number now belongs to another process.

    Check

    cat /run/reportd.pid
    ps -fp 1187   # another program: the file is stale

    Fix

    Remove the stale PID file, then start the service. Do not kill that PID: it is someone else's.

  2. It really is running: a copy started by hand, outside systemd.

    Check

    pgrep -a reportd   # a real reportd: a second copy is running

    Fix

    Stop that copy, then start the service through systemctl.

Prevent it

Keep PID files in /run (cleared at boot) and let systemd track the main PID.

Now learn it: break and fix it yourself in Already running, but it is not, then take Level 05 · Processes and signals. Open in the interactive reference · Link to this answer

Processes

fork: Resource temporarily unavailable

-bash: fork: retry: Resource temporarily unavailable

What it means

bash tried to create a new process (fork) and the kernel refused because a limit was reached. "Resource temporarily unavailable" (EAGAIN) means a process or thread limit; "Cannot allocate memory" means memory or PIDs ran out.

Causes, most likely first

  1. A user or a service hit its process limit (ulimit -u, or TasksMax= for a service). Threads count too.

    Check

    ulimit -u   # the limit for this user
    ps -eo user= | sort | uniq -c | sort -rn | head   # one user near it
    systemctl show -p TasksMax -p TasksCurrent SERVICE   # current close to max

    Fix

    Stop the runaway: systemctl stop SERVICE, or pkill -u USER NAME. Raise the limit only once you know the load is real: TasksMax= in a drop-in, or nproc in /etc/security/limits.d/.

  2. A fork loop, or a program that leaks threads. The count only climbs.

    Check

    ps -eLf | wc -l   # threads on the box; run it twice

    Fix

    Stop the program and fix the leak; a higher limit only delays it.

  3. The whole box ran out of PIDs (kernel.pid_max) or of memory (fork: Cannot allocate memory).

    Check

    cat /proc/sys/kernel/pid_max /proc/loadavg   # 4th field of loadavg: running/total tasks near pid_max
    free -h

    Fix

    Find and stop what holds the tasks or memory.

Prevent it

Keep TasksMax= on services so one cannot exhaust the box, and alert on thread counts.

Now learn it: break and fix it yourself in Too many open files · A process eating the CPU, then take Level 05 · Processes and signals. Open in the interactive reference · Link to this answer

Processes

Killed, with no error in the log

Out of memory: Killed process 2759 (java) total-vm:5832104kB, anon-rss:3201552kB

What it means

The kernel killed the process because memory ran out. "Out of memory: Killed process 2759 (java)" names the victim; anon-rss is how much memory it really used. The program gets SIGKILL, so it writes nothing to its own log.

Causes, most likely first

  1. The machine ran out of memory and the kernel's OOM killer chose this process, usually the biggest.

    Check

    sudo journalctl -k | grep -i -e 'out of memory' -e oom-kill   # plain Out of memory: the whole box
    free -h   # now, not at the time

    Fix

    Size the heap or workers to the machine (e.g. -Xmx1g), add memory, or move a workload away.

  2. A cgroup limit was hit (MemoryMax=, or a container's memory limit). The box has memory free, but this group may not use more.

    Check

    sudo journalctl -k | grep -i 'memory cgroup out of memory'   # a line here: a limit
    systemctl show reports -p MemoryMax -p MemoryCurrent

    Fix

    Raise MemoryMax= only if the service really needs more; otherwise find what grew.

  3. No OOM line at all: something else sent SIGKILL, such as a supervisor or a timeout.

    Check

    sudo journalctl -k | grep -ci oom   # 0: not the OOM killer

    Fix

    See status=9/KILL.

Prevent it

Set MemoryMax= per service and alert on available memory, not on used.

Now learn it: break and fix it yourself in Killed by the OOM killer, then take Level 14 · Performance. Open in the interactive reference · Link to this answer

Services

Services

status=203/EXEC

orders-api.service: Failed to locate executable /usr/local/bin/orders-api: No such file or directory

What it means

systemd could not start the program in ExecStart=. 203/EXEC is systemd's code for "the exec step failed": the process was never started, so there is no log from the program, only from systemd. The journal line after it gives the reason.

Causes, most likely first

  1. ExecStart points at a path that does not exist (a typo, or the binary was not installed).

    Check

    journalctl -u orders-api -n 10   # No such file or directory: this is your cause
    ls -l /usr/local/bin/orders-api

    Fix

    Fix ExecStart (absolute path), then sudo systemctl daemon-reload && sudo systemctl restart orders-api.

  2. The file is not executable, or is a script with CRLF in its shebang.

    Check

    ls -l /usr/local/bin/orders-api   # no x
    file /usr/local/bin/orders-api   # with CRLF line terminators

    Fix

    chmod 755 the file, or convert the line endings.

  3. A bare command name that is not in systemd's fixed search path. systemd does not use your shell's PATH.

    Check

    systemctl cat orders-api | grep ExecStart   # a name without /

    Fix

    Write the absolute path in ExecStart.

  4. On Rocky/RHEL, SELinux blocks a binary with the wrong label, usually one moved from a home folder.

    Check

    journalctl -u orders-api -n 10   # Permission denied
    ls -Z /usr/local/bin/orders-api   # user_home_t: moved from a home folder

    Fix

    sudo restorecon -v /usr/local/bin/orders-api.

Prevent it

Install binaries with the package that ships the unit; check with systemd-analyze verify.

Now learn it: break and fix it yourself in status=203/EXEC, then take Level 06 · systemd services. Open in the interactive reference · Link to this answer

Services

Unit is masked

Failed to start nginx.service: Unit nginx.service is masked.

What it means

Masking links a unit to /dev/null so that nothing can start it, not even by hand or as a dependency. Someone did that on purpose, often during maintenance, and systemd refuses every start.

Cause

  1. Someone ran systemctl mask, linking the unit to /dev/null.

    Check

    systemctl is-enabled nginx   # masked
    ls -l /etc/systemd/system/nginx.service   # -> /dev/null

    Fix

    Ask why it was masked, then sudo systemctl unmask nginx && sudo systemctl enable --now nginx.

Prevent it

Prefer disable for maintenance; leave a note when you mask.

Now learn it: break and fix it yourself in Unit is masked, then take Level 06 · systemd services. Open in the interactive reference · Link to this answer

Services

Unit file changed on disk

Warning: The unit file, source configuration file or drop-ins of api.service changed on disk. Run 'systemctl daemon-reload' to reload units.

What it means

systemd keeps its own copy of each unit file, read at the last daemon-reload. You edited the file on disk, so systemd warns that its copy is older, and a restart still uses the old settings.

Cause

  1. systemd works from the copy it loaded at the last daemon-reload, not from the file on disk.

    Check

    systemctl show api -p NeedDaemonReload   # yes: the files changed since systemd loaded them
    systemctl cat api   # the files on disk

    Fix

    sudo systemctl daemon-reload && sudo systemctl restart api.

Prevent it

Use systemctl edit (it reloads for you), or always daemon-reload after editing.

Now learn it: break and fix it yourself in The unit change did not take · status=203/EXEC, then take Level 06 · systemd services. Open in the interactive reference · Link to this answer

Services

Service missing after every reboot

Loaded: loaded (/etc/systemd/system/metrics-agent.service; disabled; preset: enabled)

What it means

disabled in the Loaded line means nothing starts this unit at boot. preset: enabled is only the distribution's suggestion, not the current state. A service you started by hand runs until the next reboot.

Causes, most likely first

  1. It was started but never enabled. On Rocky/RHEL packages install services disabled; Ubuntu usually enables them, so habits from one break on the other.

    Check

    systemctl is-enabled metrics-agent   # disabled: this is your cause

    Fix

    sudo systemctl enable --now metrics-agent.

  2. The unit has no [Install] section, so enable has nothing to link.

    Check

    systemctl is-enabled metrics-agent   # static
    systemctl show -p WantedBy metrics-agent   # empty: nothing starts it at boot

    Fix

    Add [Install] with WantedBy=multi-user.target, daemon-reload, enable.

  3. It is enabled, but its dependency fails at boot, so it is skipped or fails too.

    Check

    journalctl -b -u metrics-agent   # it tried and failed, or a dependency failed

    Fix

    Fix the dependency the journal names.

Prevent it

Reboot new servers once before production to prove they come back.

Now learn it: break and fix it yourself in Gone after every reboot · Fails only after a reboot, then take Level 06 · systemd services. Open in the interactive reference · Link to this answer

Services

Failed to restart: Access denied

Failed to restart ssh.service: Access denied

What it means

systemctl asked systemd to restart a system service and systemd refused because your user may not manage units. systemd checks that through polkit; with no way to ask for a password it answers "Access denied".

Causes, most likely first

  1. Managing system services needs root, or a polkit rule that allows your user.

    Check

    id   # not root
    sudo -l   # whether sudo allows it

    Fix

    sudo systemctl restart ssh (sshd on Rocky). For CI, a narrow sudoers rule for that exact command.

  2. Over ssh or in a script there is no polkit agent to ask for a password, so it says Access denied instead of prompting.

    Check

    tty   # not a tty: nothing could prompt

    Fix

    Use sudo, or a NOPASSWD rule for that exact command.

Prevent it

Grant exact commands in sudoers, not ALL.

Now learn it: break and fix it yourself in Sorry, user deploy is not allowed to execute, then take Level 06 · systemd services. Open in the interactive reference · Link to this answer

Services

Start request repeated too quickly

billing.service: Start request repeated too quickly.

What it means

The service crashed and systemd restarted it, again and again. After too many starts in a short time (StartLimitBurst= within StartLimitIntervalSec=, 5 in 10 s by default) systemd stops trying and refuses even manual starts. The crash is the real problem; this message only says systemd gave up.

Cause

  1. The service crashed more than StartLimitBurst times within StartLimitIntervalSec.

    Check

    systemctl status billing   # Result: start-limit-hit
    journalctl -u billing -n 50   # the first error, before the restarts, is the real one

    Fix

    Fix the cause, then sudo systemctl reset-failed billing && sudo systemctl start billing.

Prevent it

Validate config before deploying it; use RestartSec= so retries are spaced out.

Now learn it: break and fix it yourself in Start request repeated too quickly, then take Level 07 · Logs and failing services. Open in the interactive reference · Link to this answer

Services

Address already in use

nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)

What it means

nginx tried to bind (claim) port 80 on every address (0.0.0.0) and the kernel said another socket already holds it. Only one program may listen on an address and port, so nginx cannot start.

Causes, most likely first

  1. Another service owns the port, such as Apache or an old instance.

    Check

    sudo ss -tlnp 'sport = :80'   # users:(("apache2"...)) or httpd: this is your cause

    Fix

    sudo systemctl disable --now apache2 (httpd on Rocky), or move one service to another port.

  2. A second copy of the same service is already running.

    Check

    sudo ss -tlnp 'sport = :80'   # nginx itself owns it
    pgrep -a nginx   # more than one master

    Fix

    Stop the extra copy, then manage it only through systemctl.

  3. A container publishes the port.

    Check

    sudo ss -tlnp 'sport = :80'   # docker-proxy

    Fix

    Change that container's -p mapping.

  4. A systemd .socket unit holds it for socket activation.

    Check

    sudo ss -tlnp 'sport = :80'   # systemd
    systemctl list-sockets | grep :80

    Fix

    sudo systemctl disable --now NAME.socket.

Prevent it

Watch what package installs pull in, and pin ports per service.

Now learn it: break and fix it yourself in Address already in use, then take Level 07 · Logs and failing services. Open in the interactive reference · Link to this answer

Services

Job failed because the control process exited with error code

Job for nginx.service failed because the control process exited with error code.

What it means

systemctl's own message: the start command failed, but it does not say why. The "control process" is the start step (for nginx, the config test and the daemon starting up). The reason is in the journal.

Causes, most likely first

  1. The config is invalid. nginx tests it before starting and exits with an error.

    Check

    sudo nginx -t   # an error with a file and line number: this is your cause

    Fix

    Fix the line it names, then start again.

  2. Something the daemon needs fails: the port is in use, a file is missing, or a permission.

    Check

    journalctl -xeu nginx.service   # the first error line is the cause

    Fix

    Fix what the journal names (see Address already in use for ports).

  3. On Rocky/RHEL, an SELinux denial stops it.

    Check

    sudo ausearch -m avc -ts recent   # a denial for nginx

    Fix

    Fix the label, port type or boolean (see the SELinux entries).

Prevent it

Test configs (nginx -t, apachectl configtest) before reload.

Now learn it: break and fix it yourself in Address already in use, then take Level 07 · Logs and failing services. Open in the interactive reference · Link to this answer

Services

Connection refused only after a reboot

Error: connect ECONNREFUSED 127.0.0.1:5432

What it means

ECONNREFUSED means nothing was listening on 127.0.0.1:5432 when the app tried to connect. It happens only after a reboot: at boot the app started before PostgreSQL was ready, while by hand PostgreSQL is already running.

Causes, most likely first

  1. It starts before the service it needs: there is no After= ordering, so systemd starts both at once.

    Check

    systemctl cat inventory | grep -E 'After|Wants|Requires'   # no postgresql: this is your cause
    journalctl -b -u inventory -u postgresql   # inventory failed before postgresql was up

    Fix

    A drop-in with [Unit] After=postgresql.service Wants=postgresql.service; daemon-reload; reboot to test.

  2. The dependency has started but does not accept connections yet, or runs on another host: After= cannot help.

    Check

    journalctl -b -u inventory -u postgresql   # inventory failed after postgresql started

    Fix

    Make the app retry its first connection with backoff.

Prevent it

Declare every local dependency with After= plus Wants= or Requires=.

Now learn it: break and fix it yourself in Fails only after a reboot, then take Level 07 · Logs and failing services. Open in the interactive reference · Link to this answer

Services

The unit files have no installation config

The unit files have no installation config (WantedBy=, RequiredBy=, UpheldBy=,

What it means

systemctl enable creates links from a target (such as multi-user.target) to the unit, using the unit's [Install] section. This unit has none, so there is nothing to enable.

Causes, most likely first

  1. The unit has no [Install] section with WantedBy=.

    Check

    systemctl cat NAME   # no [Install] section

    Fix

    Add [Install] with WantedBy=multi-user.target, daemon-reload, enable.

  2. It is meant to be started by something else, such as a .timer or a .socket.

    Check

    systemctl list-timers --all | grep NAME   # a timer runs it

    Fix

    Enable the timer instead: sudo systemctl enable --now NAME.timer.

Prevent it

Start unit files from a template that includes [Install].

Now learn it: break and fix it yourself in Gone after every reboot, then take Level 06 · systemd services. Open in the interactive reference · Link to this answer

Services

Unit not found

Failed to restart mysql.service: Unit mysql.service not found.

What it means

systemd has no unit file with that name loaded. Either it does not exist on this machine, it has another name here, or systemd has not reloaded since it was added.

Causes, most likely first

  1. The package is not installed, or the service has another name here (mysql on Ubuntu, mysqld or mariadb on Rocky; apache2 or httpd).

    Check

    systemctl list-unit-files | grep -i -e mysql -e maria   # the real name, or nothing

    Fix

    Use the real name from list-unit-files, or install the package that provides the service.

  2. A unit file was added or renamed without systemctl daemon-reload.

    Check

    ls /etc/systemd/system /usr/lib/systemd/system | grep -i sql   # the file is there

    Fix

    sudo systemctl daemon-reload.

  3. A typo, or a user service that needs systemctl --user.

    Check

    systemctl --user list-unit-files | grep -i NAME

    Fix

    Correct the name, or use systemctl --user.

Prevent it

Check systemctl list-unit-files before you write a unit name into a script or runbook.

Now learn it: break and fix it yourself in The unit change did not take, then take Level 06 · systemd services. Open in the interactive reference · Link to this answer

Services

Unit has a bad unit file setting

Failed to start worker.service: Unit worker.service has a bad unit file setting.

What it means

systemd read the unit file and found a line it cannot use, so it refuses to start the unit. The journal says which line.

Causes, most likely first

  1. A required line is missing, such as ExecStart= (the journal says: Service has no ExecStart=...).

    Check

    journalctl -b -u worker | tail   # Service has no ExecStart=

    Fix

    Add the line, then sudo systemctl daemon-reload && sudo systemctl start worker.

  2. A typo in a key or a section name, or a value systemd cannot parse.

    Check

    systemd-analyze verify /etc/systemd/system/worker.service   # names the file and line
    systemctl cat worker

    Fix

    Fix the line it names, daemon-reload, start.

Prevent it

Run systemd-analyze verify on every unit file before you install it.

Now learn it: break and fix it yourself in status=203/EXEC · The unit change did not take, then take Level 06 · systemd services. Open in the interactive reference · Link to this answer

Services

Exec format error

-bash: ./node_exporter: cannot execute binary file: Exec format error

What it means

The kernel was asked to run a file and did not recognise it as a program it can run. "Exec format error" (ENOEXEC) means the file is not a binary for this CPU, and not a script with a #! line.

Causes, most likely first

  1. The binary is built for another CPU: an arm64 build on x86_64, or the other way round.

    Check

    uname -m   # x86_64 or aarch64
    file ./node_exporter   # ELF ... ARM aarch64 on an x86_64 box: this is your cause

    Fix

    Download the build for your architecture (amd64 for x86_64, arm64 for aarch64).

  2. A script without a #! first line, started by systemd or exec. Without it the kernel does not know what runs it.

    Check

    head -1 SCRIPT   # must start with #!

    Fix

    Add a shebang such as #!/bin/bash as the first line.

  3. The download is an archive or an HTML error page, not the program.

    Check

    file ./node_exporter   # gzip compressed data or HTML document

    Fix

    Unpack the archive, or download again from the right URL.

Prevent it

Pick downloads by uname -m, and give every script a #! line.

Now learn it: break and fix it yourself in status=203/EXEC, then take Level 06 · systemd services. Open in the interactive reference · Link to this answer

Services

System has not been booted with systemd

System has not been booted with systemd as init system (PID 1). Can't operate.

What it means

systemctl talks to systemd running as PID 1, the first process. Here PID 1 is something else, so there is no systemd to talk to.

Causes, most likely first

  1. In a container, PID 1 is your program. Docker and most containers do not run systemd.

    Check

    ps -p 1 -o comm=   # bash, node or your app, not systemd

    Fix

    Run the program in the foreground as the container's command, not with systemctl.

  2. WSL started without systemd.

    Check

    ps -p 1 -o comm=   # init, not systemd

    Fix

    Add [boot] and systemd=true to /etc/wsl.conf, run wsl --shutdown in Windows, then reopen it.

Prevent it

Practise services on a VM or a cloud instance, where systemd is PID 1; keep a container to one foreground process.

Now learn it: break and fix it yourself in Gone after every reboot, then take Level 06 · systemd services. Open in the interactive reference · Link to this answer

Services

Cron ignores a file in /etc/cron.d

(*system*db-backup) ERROR (Syntax error, this crontab file will be ignored)

What it means

cron read a file in /etc/cron.d, could not parse a line, and ignored the whole file. *system*db-backup is cron's name for the system file /etc/cron.d/db-backup. None of its jobs run until it is fixed.

Causes, most likely first

  1. /etc/cron.d lines need a user field after the five time fields. Without it, cron takes the command's first word as the user name.

    Check

    cat /etc/cron.d/db-backup   # five fields then the command, no user: this is your cause
    journalctl -u cron -n 20   # bad username (crond on Rocky, or /var/log/cron)

    Fix

    Add the user: */10 * * * * postgres pg_dump ....

  2. Wrong owner or a writable mode. cron refuses files not owned by root or writable by others.

    Check

    ls -l /etc/cron.d   # owned by root, not group or world writable

    Fix

    sudo chown root:root FILE && sudo chmod 644 FILE.

  3. On Ubuntu and Debian, a dot in the file name. cron skips the file without a word, so its jobs never run.

    Check

    ls /etc/cron.d   # db-backup.cron or similar

    Fix

    Rename it with letters, digits, - and _ only.

Prevent it

Name cron.d files with letters, digits, - and _ only, and run each job as the user that owns the work.

Now learn it: break and fix it yourself in Cron job as the wrong user, then take Level 08 · Scheduling with cron. Open in the interactive reference · Link to this answer

Services

new crontab file is missing newline before EOF

new crontab file is missing newline before EOF, can't install.

What it means

crontab FILE checks the file before installing it and refuses one whose last line does not end with a newline, because cron would drop that last line.

Cause

  1. The last line has no newline (printf without \n is the usual source).

    Check

    tail -c1 file | od -c   # the last character is not \n

    Fix

    echo >> file, then crontab file.

Prevent it

Use echo or a here-document when generating crontabs.

Now learn it: break and fix it yourself in Cron job that never works, then take Level 08 · Scheduling with cron. Open in the interactive reference · Link to this answer

Services

pg_dump: role "root" does not exist

pg_dump: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL:  role "root" does not exist

What it means

PostgreSQL accepted the connection on the local socket, then looked for a database role named "root" and found none. With no -U, the client uses your Linux user name as the role name.

Causes, most likely first

  1. No -U given: the client uses the Linux user name as the role (root here), and no such role exists.

    Check

    sudo -u postgres psql -c '\du'   # the roles that exist; root is not among them

    Fix

    Run the job as postgres: sudo -u postgres pg_dump ..., or user postgres in cron.d. Better: give the job its own role and pass -U.

  2. PGUSER is set to a role that does not exist.

    Check

    printenv PGUSER PGHOST PGPORT   # what the job inherits

    Fix

    Unset or correct PGUSER.

  3. It reached another cluster or port where that role was never created.

    Check

    pg_lsclusters   # Ubuntu: which cluster listens on which port

    Fix

    Pass the right -p (or -h) for the cluster. Do not create a superuser role named root to silence it.

Prevent it

Give automation its own role and Linux user.

Now learn it: break and fix it yourself in Cron job as the wrong user, then take Level 08 · Scheduling with cron. Open in the interactive reference · Link to this answer

Services

Variable set in my shell, missing in the service

Error: DATABASE_URL is not set

What it means

The program started without a variable it needs. Under systemd a service gets only the environment its unit gives it: nothing from your login shell.

Causes, most likely first

  1. Services do not read ~/.bashrc, ~/.profile, your exports or /etc/environment.

    Check

    sudo cat /proc/$(pgrep -o reports)/environ | tr '\0' '\n' | cut -d= -f1   # the names the running process has, without values

    Fix

    A drop-in with EnvironmentFile=/etc/app/app.env (mode 600), then daemon-reload and restart. Keep secrets out of Environment= in the unit: any user can read it with systemctl show.

  2. The EnvironmentFile= path is wrong. With a leading - systemd skips a missing file silently.

    Check

    systemctl show reports-api -p EnvironmentFiles   # the files it loads; ignore_errors=yes means a missing one is skipped
    ls -l /etc/app/app.env

    Fix

    Fix the path, daemon-reload and restart.

  3. The variable is set for a different user than the service runs as.

    Check

    systemctl show -p User reports-api

    Fix

    Set it in the unit or its EnvironmentFile, not in a user's profile.

Prevent it

Keep a service's configuration in its unit or an EnvironmentFile under /etc, never in a login shell.

Now learn it: break and fix it yourself in Works in my shell, not as a service, then take Level 06 · systemd services. Open in the interactive reference · Link to this answer

Disks

Disks

No space left on device

touch: cannot touch '/tmp/x': No space left on device

What it means

The kernel could not write because the file system had nothing left to give: no free blocks, or no free inodes (the slots that hold one file each). ENOSPC is the code; the path tells you which file system to check.

Causes, most likely first

  1. The file system is full.

    Check

    df -h /tmp   # Use% 100 on the file system that holds the path
    sudo du -xh --max-depth=1 / 2>/dev/null | sort -h | tail   # the biggest folders

    Fix

    Delete or compress what you do not need; truncate a runaway log (truncate -s 0) rather than rm it.

  2. Inodes are exhausted: space is left, but no new file can be created.

    Check

    df -i /tmp   # IUse% 100: inodes, not space

    Fix

    Remove the small files (see the inodes entry).

  3. Deleted files are still held open. The space returns only when the process closes them.

    Check

    sudo lsof +L1   # (deleted) files still taking space

    Fix

    Restart the process that holds them.

  4. Only root's reserve is left (5% on ext4 by default), so normal users fail first.

    Check

    df -h /   # Avail 0 while Use% shows about 95%

    Fix

    Free space as above. Lowering the reserve (tune2fs -m) buys minutes, not a fix.

Prevent it

Alert at 80%, rotate logs by size, keep data on its own volume.

Now learn it: break and fix it yourself in Disk full, but du finds nothing · A debug log filling the disk · No space left, with space left, then take Level 09 · Disk space. Open in the interactive reference · Link to this answer

Disks

df says full, du finds much less

/dev/nvme0n1p1   20G   20G     0 100% /

What it means

df asks the file system how much is used; du adds up the files it can see. When df says much more, the space is used by something du cannot see.

Causes, most likely first

  1. A deleted file is still open by a process. It has no name, so du misses it, but its space stays used.

    Check

    sudo lsof +L1   # (deleted) files and the process holding them

    Fix

    sudo systemctl restart SERVICE, or sudo truncate -s 0 /proc/PID/fd/N.

  2. Files are hidden under a mount point: they were written before the disk was mounted over that folder.

    Check

    sudo mkdir -p /mnt/rootfs && sudo mount -o bind,ro / /mnt/rootfs && sudo du -sh /mnt/rootfs/data   # space under the /data mount point; umount /mnt/rootfs after

    Fix

    Stop the writer, unmount, move the files, mount again.

  3. du ran without sudo and skipped folders it could not read.

    Check

    sudo du -xsh /   # compare with df

    Fix

    Run du with sudo.

Prevent it

Use logrotate copytruncate (or a reopen signal); never rm a log a process writes.

Now learn it: break and fix it yourself in Disk full, but du finds nothing · A debug log filling the disk, then take Level 09 · Disk space. Open in the interactive reference · Link to this answer

Disks

No space left, with space left (inodes)

/dev/nvme0n1p1 1310720 1310720       0  100% /

What it means

Every file needs an inode, and this file system has none left: IUse% is 100%. Space is free, but no new file, not even an empty one, can be created, so it reads as "No space left on device".

Causes, most likely first

  1. Millions of tiny files: sessions, cache, mail queues.

    Check

    df -i   # IUse% 100
    sudo du --inodes -x / 2>/dev/null | sort -n | tail   # the folders with the most files

    Fix

    sudo find DIR -type f -mmin +1440 -delete for the folder that grew.

  2. Mostly on ext4, which fixes the inode count when the file system is made. XFS (Rocky's default) allocates inodes as needed, so this is rare there.

    Check

    df -T /   # ext4

    Fix

    Clean up as above; for a volume that will always hold many small files, make it with more inodes (mkfs.ext4 -i).

Prevent it

Schedule cleanup of session and cache folders; alert on inode use.

Now learn it: break and fix it yourself in No space left, with space left, then take Level 09 · Disk space. Open in the interactive reference · Link to this answer

Disks

Read-only file system

touch: cannot touch '/data/x': Read-only file system

What it means

The kernel refused the write because the file system is mounted read-only (EROFS). Root cannot override that. The question is who made it read-only and why.

Causes, most likely first

  1. ext4 found an error and remounted itself read-only (errors=remount-ro) to protect your data.

    Check

    sudo dmesg -T | grep -i -e ext4 -e 'read-only'   # Remounting filesystem read-only: this is your cause

    Fix

    Stop writers, snapshot the volume if you can, umount, run fsck on the device, mount again. Do not force rw on a damaged file system.

  2. The disk went away or returned I/O errors, such as a detached cloud volume.

    Check

    sudo dmesg -T | grep -i -e 'I/O error' -e xfs   # I/O errors or an XFS shutdown

    Fix

    Fix the disk or attachment; for XFS run xfs_repair on the unmounted device.

  3. The root file system in emergency mode, or mounted ro in fstab.

    Check

    findmnt -no OPTIONS -T /data   # ro here: the mount itself is read-only

    Fix

    In emergency mode: mount -o remount,rw /; otherwise fix the fstab options.

  4. Read-only for one service only: ProtectSystem=strict or ReadOnlyPaths= in its unit.

    Check

    systemctl show SERVICE -p ProtectSystem -p ReadWritePaths   # strict, and the path is not listed

    Fix

    Allow the path with ReadWritePaths= in a drop-in.

Prevent it

Watch dmesg and disk health; keep backups. Do not force rw on a damaged file system.

Now learn it: break and fix it yourself in Read-only file system · Emergency mode after an fstab change, then take Level 10 · Mounts and file systems. Open in the interactive reference · Link to this answer

Disks

umount: target is busy

umount: /data: target is busy.

What it means

umount refuses while anything still uses the file system: an open file, a process's working directory, or another mount on top of it.

Causes, most likely first

  1. A process has a file open or its working directory there (your own shell counts).

    Check

    sudo fuser -vm /data   # the processes using it
    sudo lsof +D /data

    Fix

    Stop the service, cd out, then umount.

  2. Something else uses it: a mount inside it, a swap file or a loop device.

    Check

    findmnt -R /data; swapon --show; losetup -a   # mounts, swap and loop devices on it

    Fix

    Unmount inner mounts or swapoff first. Avoid umount -l unless you know what holds it: the writes carry on unseen.

Prevent it

RequiresMountsFor= in units makes the order explicit.

Now learn it: break and fix it yourself in Read-only file system, then take Level 10 · Mounts and file systems. Open in the interactive reference · Link to this answer

Disks

e2fsck: Cannot continue, aborting

/dev/nvme1n1p1 is mounted.
e2fsck: Cannot continue, aborting.

What it means

e2fsck refuses to check a mounted file system, because changing it while the kernel also writes to it would corrupt it.

Cause

  1. The file system is mounted.

    Check

    findmnt /dev/nvme1n1p1   # a TARGET: it is mounted

    Fix

    Unmount it first; for / use a rescue boot or a check at boot.

Prevent it

Keep data on separate volumes you can unmount.

Now learn it: break and fix it yourself in Read-only file system, then take Level 10 · Mounts and file systems. Open in the interactive reference · Link to this answer

Disks

resize2fs: Nothing to do!

The filesystem is already 5214459 (4k) blocks long.  Nothing to do!

What it means

resize2fs grows a file system to fill its partition. It found the file system already as big as the partition, so there is nothing to grow: the space you added is in a layer below.

Causes, most likely first

  1. The partition was not grown; the file system already fills it.

    Check

    lsblk   # disk bigger than the partition: this is your cause

    Fix

    sudo growpart /dev/nvme0n1 1 && sudo resize2fs /dev/nvme0n1p1 (xfs_growfs / for XFS; growpart is in cloud-guest-utils on Ubuntu, cloud-utils-growpart on Rocky).

  2. On LVM: the physical and logical volumes were not grown.

    Check

    sudo pvs; sudo lvs   # PFree or VFree unused

    Fix

    growpart, then sudo pvresize /dev/nvme0n1p3 && sudo lvextend -r -l +100%FREE VG/LV.

  3. The kernel has not seen the bigger disk yet.

    Check

    lsblk   # the disk still at the old size

    Fix

    Rescan it (for a SCSI disk, echo 1 | sudo tee /sys/class/block/sdb/device/rescan) or reboot.

Prevent it

Script the three steps: volume, partition, file system.

Now learn it: break and fix it yourself in The disk grew, the filesystem did not, then take Level 10 · Mounts and file systems. Open in the interactive reference · Link to this answer

Disks

You are in emergency mode

Dependency failed for local-fs.target - Local File Systems.

What it means

systemd could not mount a file system listed in /etc/fstab, so local-fs.target failed and it stopped at an emergency shell rather than boot with data missing.

Causes, most likely first

  1. An fstab line for a disk that is missing or has a wrong UUID, without nofail.

    Check

    journalctl -xb | grep -i -e failed -e 'timed out'   # Timed out waiting for device: this is your cause
    blkid   # compare the UUIDs with /etc/fstab
    cat /etc/fstab

    Fix

    mount -o remount,rw /, fix fstab, systemctl daemon-reload, mount -a, then systemctl default.

  2. A file system that fails its check.

    Check

    journalctl -xb | grep -i fsck   # errors from the check

    Fix

    Run fsck (ext4) or xfs_repair (XFS) on that unmounted device, then systemctl default.

Prevent it

Run mount -a after every fstab change; use nofail for non-critical disks.

Now learn it: break and fix it yourself in Emergency mode after an fstab change, then take Level 13 · Boot, kernel and packages. Open in the interactive reference · Link to this answer

Disks

mount: can't find UUID

mount: /data: can't find UUID=7c1e0b6a-90d1-4b8e-a3f5-2e64c2a71b9f.

What it means

fstab names the disk by UUID, and no attached block device has that UUID, so mount does not know which disk you mean.

Causes, most likely first

  1. A typo in the UUID, or the disk was replaced.

    Check

    sudo blkid   # the real UUIDs
    lsblk -f

    Fix

    Copy the UUID from blkid into /etc/fstab, then sudo mount -a.

  2. The disk was formatted again (mkfs gives it a new UUID) or is not attached.

    Check

    lsblk -f   # the disk is missing, or has another UUID

    Fix

    Attach it, or update fstab with the new UUID.

Prevent it

Paste UUIDs, never type them.

Now learn it: break and fix it yourself in Emergency mode after an fstab change, then take Level 13 · Boot, kernel and packages. Open in the interactive reference · Link to this answer

Disks

mount: wrong fs type, bad option, bad superblock

mount: /mnt/data: wrong fs type, bad option, bad superblock on /dev/sdc, missing codepage or helper program, or other error.

What it means

mount could not make sense of what is on the device with the type and options given. The message lists every possible reason because the kernel only reports a generic failure; dmesg usually has the specific one.

Causes, most likely first

  1. The device has no file system yet (a new volume).

    Check

    lsblk -f   # FSTYPE empty
    sudo blkid /dev/sdc   # no output

    Fix

    Only for a new, empty volume: sudo mkfs.ext4 /dev/sdc (this erases it).

  2. You mounted the whole disk (/dev/sdc) and the file system is on a partition (/dev/sdc1).

    Check

    lsblk -f   # the FSTYPE is on sdc1

    Fix

    Mount the partition and type that lsblk -f shows.

  3. The helper is missing: nfs-common (nfs-utils on Rocky) for NFS, cifs-utils for SMB.

    Check

    ls /sbin/mount.nfs /sbin/mount.cifs   # missing

    Fix

    sudo apt install nfs-common or cifs-utils (dnf install nfs-utils on Rocky).

  4. A misspelled mount option.

    Check

    sudo dmesg | tail   # unknown parameter or bad option

    Fix

    Fix the option.

Prevent it

Check lsblk -f before mount or mkfs, and test every fstab line with sudo mount -a.

Now learn it: break and fix it yourself in Emergency mode after an fstab change · The disk grew, the filesystem did not, then take Level 10 · Mounts and file systems. Open in the interactive reference · Link to this answer

Disks

logrotate: parent directory has insecure permissions

error: skipping "/var/log/orders/orders.log" because parent directory has insecure permissions (It's world writable or writable by group which is not "root") Set "su" directive in config file to tell logrotate which user/group should be used for rotation.

What it means

logrotate refuses to rotate logs in a folder that someone other than root can write to, because a user could swap files and trick root into overwriting something else. It skips the file, so the log grows forever.

Cause

  1. The log folder is writable by a non-root group or by everyone.

    Check

    ls -ld /var/log/orders   # w for group or others
    sudo logrotate -d /etc/logrotate.d/orders   # the same message in the dry run

    Fix

    Add su orders orders to the rule, or chmod 755 the folder; then sudo logrotate -f to test.

Prevent it

Run logrotate -d after every new rule.

Now learn it: break and fix it yourself in logrotate is not rotating, then take Level 09 · Disk space. Open in the interactive reference · Link to this answer

Disks

Space not freed after log rotation

orders    1204 orders    3w   REG    8,1 12025908019     0  131003 /var/log/orders/orders.log.1 (deleted)

What it means

logrotate renamed the log and later deleted it, but the program still has the old file open and keeps writing there. (deleted) in lsof means the space is still in use until the program closes the file.

Causes, most likely first

  1. The rule renames the file (create) while the program keeps it open; compress then deletes it.

    Check

    sudo lsof +L1   # .log.1 (deleted) held by the program

    Fix

    Restart the program once to release the space.

  2. No copytruncate and no postrotate reload, so the program is never told to reopen.

    Check

    grep -E 'copytruncate|postrotate' /etc/logrotate.d/*

    Fix

    Add postrotate with the program's reload command, or copytruncate (it can lose lines written during the copy).

Prevent it

For programs that cannot reopen logs, use copytruncate; better, log to the journal.

Now learn it: break and fix it yourself in logrotate is not rotating, then take Level 09 · Disk space. Open in the interactive reference · Link to this answer

Disks

The journal is using gigabytes

Archived and active journals take up 9.2G in the file system.

What it means

systemd's journal stores logs under /var/log/journal. By default journald keeps it to 10% of the file system, capped at 4G, so a larger figure means a setting or leftover files.

Causes, most likely first

  1. SystemMaxUse= was raised somewhere.

    Check

    systemd-analyze cat-config systemd/journald.conf | grep -i -e maxuse -e keepfree   # the settings in effect, drop-ins included

    Fix

    sudo journalctl --vacuum-size=500M, and SystemMaxUse=500M in a drop-in under /etc/systemd/journald.conf.d/, then sudo systemctl restart systemd-journald.

  2. Journal folders from another machine ID, such as an image cloned without a new /etc/machine-id, that journald does not clean.

    Check

    ls /var/log/journal; cat /etc/machine-id   # folders that are not this machine's ID

    Fix

    Remove those folders once you no longer need their logs.

Prevent it

Cap the journal on every server image; ship logs off the box.

Now learn it: break and fix it yourself in The journal is eating the disk, then take Level 09 · Disk space. Open in the interactive reference · Link to this answer

Disks

Stale file handle

ls: cannot access '/mnt/shared': Stale file handle

What it means

An NFS client refers to files by handles the server gave it. The server no longer recognises them, because the export was restored, moved or recreated, so every access fails.

Cause

  1. The export was restored, moved or recreated; the client's handles point at nothing.

    Check

    findmnt -t nfs4
    df -h 2>&1 | grep -i stale   # the stale mount

    Fix

    sudo umount -l /mnt/shared && sudo mount /mnt/shared.

Prevent it

Coordinate NFS server changes with the clients; remount after restores.

Now learn it: break and fix it yourself in Stale file handle, then take Level 10 · Mounts and file systems. Open in the interactive reference · Link to this answer

Networking

Networking

Temporary failure in name resolution

curl: (6) Could not resolve host: api.payments.example

What it means

The program asked the system resolver for the IP address of a name and got none. Name or service not known means a DNS server answered that the name does not exist; Temporary failure means no server answered. curl's (6) does not say which, so check.

Causes, most likely first

  1. The resolver answered that the name does not exist (NXDOMAIN): a typo, a missing search domain, or a private zone this resolver cannot see.

    Check

    dig api.payments.example   # status: NXDOMAIN: no such name there
    dig @10.0.0.2 api.payments.example   # the server you expect answers it: the wrong resolver is in use

    Fix

    Fix the name or search domain, or use the resolver that serves that zone.

  2. No answer at all: a wrong or unreachable nameserver, or systemd-resolved down.

    Check

    resolvectl status   # Ubuntu: the real servers behind 127.0.0.53
    dig api.payments.example   # timed out: no server answers

    Fix

    Ubuntu: set nameservers in netplan, then sudo netplan apply (/etc/resolv.conf is generated and edits are lost). Rocky: sudo nmcli connection modify NAME ipv4.dns 10.0.0.2 && sudo nmcli connection up NAME.

  3. No route or a firewall to the DNS server.

    Check

    dig @10.0.0.2 api.payments.example   # timed out, while other traffic works

    Fix

    Open UDP and TCP 53 to the resolver, or fix the route.

Prevent it

Manage DNS settings in one place (netplan, DHCP) and monitor resolution.

Now learn it: break and fix it yourself in Temporary failure in name resolution · Network is unreachable, then take Level 11 · Networking. Open in the interactive reference · Link to this answer

Networking

dig shows the right address, the app uses another

Error: connect ETIMEDOUT 10.0.2.15:5432

What it means

dig asks a DNS server directly. Programs ask the system resolver, which reads /etc/hosts first and may use a cache, so they can get a different, older address and time out connecting to it.

Causes, most likely first

  1. A stale line in /etc/hosts, which is read before DNS.

    Check

    getent hosts db.internal   # what programs get
    dig +short db.internal   # what DNS says
    grep db.internal /etc/hosts   # the old address: this is your cause

    Fix

    Remove the stale /etc/hosts line.

  2. A cache: systemd-resolved or nscd.

    Check

    getent hosts db.internal   # old address, but not in /etc/hosts

    Fix

    sudo resolvectl flush-caches.

  3. The app resolved the name once at start (a connection pool, or the JVM's DNS cache).

    Check

    getent hosts db.internal   # already right, yet the app still uses the old address

    Fix

    Restart the app, and set a DNS TTL it honours.

Prevent it

Keep /etc/hosts minimal; use DNS with sensible TTLs.

Now learn it: break and fix it yourself in dig is right, the app is wrong, then take Level 11 · Networking. Open in the interactive reference · Link to this answer

Networking

Network is unreachable

ping: connect: Network is unreachable

What it means

The kernel has no route to the destination, so it does not even try to send. That is different from a timeout: nothing left the machine.

Causes, most likely first

  1. No default route.

    Check

    ip route   # no default line
    ip route get 203.0.113.10   # Network is unreachable

    Fix

    sudo ip route add default via 10.0.1.1 dev eth0, and persist it in netplan (Ubuntu) or nmcli (Rocky).

  2. The interface is down, or got no address from DHCP.

    Check

    ip -br a   # DOWN, or no IPv4 address

    Fix

    Bring it up and fix DHCP or the static address.

  3. The name resolves to IPv6 and there is no IPv6 route.

    Check

    ip -6 route   # no default for IPv6

    Fix

    Use -4 for now, or add the IPv6 route.

Prevent it

Change routes through netplan or DHCP, not by hand.

Now learn it: break and fix it yourself in Network is unreachable, then take Level 11 · Networking. Open in the interactive reference · Link to this answer

Networking

Error: Nexthop has invalid gateway

Error: Nexthop has invalid gateway.

What it means

iproute2 refuses a route whose gateway is not on a network this machine is directly connected to, because it could not reach that gateway to hand it the packets.

Cause

  1. The gateway is not on any directly connected subnet.

    Check

    ip -br a   # your address and prefix
    ip route   # the connected subnets

    Fix

    Use a gateway inside your subnet (usually .1).

Prevent it

Check the subnet before adding routes.

Now learn it: break and fix it yourself in Network is unreachable, then take Level 11 · Networking. Open in the interactive reference · Link to this answer

Networking

Connection timed out

curl: (28) Failed to connect to web-01 port 8080 after 134106 ms: Couldn't connect to server

What it means

The client sent a connection request (a SYN) and never got any answer, so after retrying it gave up. Silence usually means something dropped the packets on the way: a firewall or a wrong route. A refusal, by contrast, would come back at once.

Causes, most likely first

  1. A firewall drops the packets: ufw or firewalld on the server, a cloud security group or network ACL on the way.

    Check

    sudo tcpdump -ni any tcp port 8080   # on the server while you retry: no SYN: dropped on the way; SYN but no reply: the host firewall
    sudo ufw status verbose   # Ubuntu; sudo firewall-cmd --list-all on Rocky

    Fix

    Host firewall: sudo ufw allow from 10.0.1.0/24 to any port 8080 proto tcp. Security group: allow the port from the client's subnet only, not 0.0.0.0/0.

  2. The address is wrong or the host is down.

    Check

    getent hosts web-01   # the address the client uses
    curl -v localhost:8080   # on the server: works locally, so the service is up

    Fix

    Use the right address, or bring the host up.

  3. Replies take another route back.

    Check

    sudo tcpdump -ni any tcp port 8080   # SYN-ACK leaves on another interface

    Fix

    Fix the return route on the server.

Prevent it

Open ports in the same change that adds the service.

Now learn it: break and fix it yourself in Works locally, not from the load balancer, then take Level 11 · Networking. Open in the interactive reference · Link to this answer

Networking

Connection refused

curl: (7) Failed to connect to 10.0.1.10 port 8080 after 0 ms: Couldn't connect to server

What it means

The client reached the host, and the host answered at once that nothing accepts connections on that port (a TCP reset). The network works; the problem is on the server side of that port.

Causes, most likely first

  1. Nothing listens on that address and port: the service is down or on another port.

    Check

    sudo ss -tlnp 'sport = :8080'   # no line: nothing listens
    systemctl status SERVICE

    Fix

    Start the service, or fix the port the client uses.

  2. The service listens on 127.0.0.1 only, so it refuses connections from other hosts.

    Check

    sudo ss -tlnp 'sport = :8080'   # 127.0.0.1:8080 or [::1]:8080: local only

    Fix

    Bind it to the private IP (0.0.0.0 only if it must be reachable everywhere) and restart.

  3. localhost resolved to ::1 while the service listens on 127.0.0.1 only.

    Check

    curl -v http://localhost:8080   # Trying [::1]:8080 then refused

    Fix

    Use 127.0.0.1 in the client, or make the service listen on both.

  4. A firewall REJECT rule. It looks the same as a closed port.

    Check

    sudo ss -tlnp 'sport = :8080'   # it does listen
    sudo nft list ruleset | grep -i reject

    Fix

    Allow the port in the firewall.

Prevent it

Check the Local Address column after every deploy.

Now learn it: break and fix it yourself in Listening on 127.0.0.1 only, then take Level 11 · Networking. Open in the interactive reference · Link to this answer

Networking

sudo: unable to resolve host

sudo: unable to resolve host web-02: Name or service not known

What it means

sudo looks up the machine's own hostname and could not resolve it. It is a warning, but sudo waits for the lookup to time out, so every sudo is slow. It usually follows a rename.

Cause

  1. The new hostname is not in /etc/hosts.

    Check

    hostname
    getent hosts $(hostname)   # no output: this is your cause

    Fix

    sudo sed -i 's/old-name/new-name/' /etc/hosts.

Prevent it

Rename with hostnamectl set-hostname and update /etc/hosts in the same step; on cloud images set the name through cloud-init, which can rewrite /etc/hosts.

Now learn it: break and fix it yourself in sudo: unable to resolve host, then take Level 11 · Networking. Open in the interactive reference · Link to this answer

Networking

SSL certificate problem: certificate is not yet valid

curl: (60) SSL certificate problem: certificate is not yet valid

What it means

Every certificate has a "not before" date. This machine's clock says it is earlier than that, so the certificate looks like it is from the future. When all TLS fails at once, the clock is almost always the cause.

Causes, most likely first

  1. The system clock is behind.

    Check

    timedatectl   # System clock synchronized: no, and a wrong time
    chronyc tracking   # Rocky (chrony): the offset

    Fix

    Ubuntu: sudo timedatectl set-ntp true (systemd-timesyncd). Rocky: sudo systemctl enable --now chronyd; sudo chronyc makestep for a large offset.

  2. The certificate was issued minutes ago and this clock is slightly behind.

    Check

    echo | openssl s_client -connect HOST:443 -servername HOST 2>/dev/null | openssl x509 -noout -startdate   # compare with date -u

    Fix

    Sync the clock as above.

Prevent it

Run an NTP client on every machine; alert on clock offset.

Now learn it: break and fix it yourself in Certificate is not yet valid, then take Level 13 · Boot, kernel and packages. Open in the interactive reference · Link to this answer

Users and security

Users and security

is not in the sudoers file

alice is not in the sudoers file.

What it means

sudo checked its rules and found none for this user or any group they are in. It refuses and logs the attempt.

Causes, most likely first

  1. No rule names the user or one of their groups (sudo on Ubuntu, wheel on Rocky).

    Check

    groups alice   # no sudo or wheel
    sudo -l -U alice   # as an admin: not allowed

    Fix

    sudo usermod -aG sudo alice (wheel on Rocky), or a narrow rule in /etc/sudoers.d via visudo.

  2. Just added to the group, but the session still has the old groups.

    Check

    groups alice   # lists sudo
    id   # in alice's session: sudo is missing

    Fix

    Log out and in again.

Prevent it

Grant admin rights through a group, reviewed regularly.

Now learn it: break and fix it yourself in Added to a group, lost the others · Sorry, user deploy is not allowed to execute, then take Level 04 · Users, groups and sudo. Open in the interactive reference · Link to this answer

Users and security

Sorry, user is not allowed to execute

Sorry, user deploy is not allowed to execute '/usr/bin/systemctl restart orders' as root on web-01.

What it means

The user has sudo rules, but none matches this exact command line. sudo compares the full path and arguments with each rule.

Causes, most likely first

  1. The rule names a different command line: another path (/bin or /usr/bin), unit name (orders or orders.service) or arguments.

    Check

    sudo -l -U deploy   # the exact commands allowed; compare letter by letter

    Fix

    Update the rule to the exact command, and check it with visudo -c.

  2. A later rule for the same user overrides it: in sudoers the last match wins.

    Check

    sudo grep -rn deploy /etc/sudoers /etc/sudoers.d   # read the order

    Fix

    Remove or reorder the later rule.

Prevent it

Keep CI sudo rules in version control, next to the units they restart.

Now learn it: break and fix it yourself in Sorry, user deploy is not allowed to execute, then take Level 12 · Security hardening. Open in the interactive reference · Link to this answer

Users and security

sudo: a terminal is required to read the password

sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper

What it means

sudo needs a password for this command and has no terminal to ask on, as in cron, CI or ssh host command. So it stops.

Cause

  1. sudo needs a password and there is no terminal to ask on.

    Check

    sudo -l -U deploy   # the rule has no NOPASSWD
    sudo -n true && echo no password needed   # as deploy: fails

    Fix

    Allow the exact command without a password, in a drop-in edited with sudo visudo -f /etc/sudoers.d/deploy: deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart orders. For one interactive run: ssh -t HOST sudo COMMAND. Never pipe a password into sudo -S.

Prevent it

Give automation NOPASSWD rules for the commands it needs and nothing more; never pipe a password into sudo -S.

Now learn it: break and fix it yourself in Sorry, user deploy is not allowed to execute · sudo: parse error in sudoers, then take Level 12 · Security hardening. Open in the interactive reference · Link to this answer

Users and security

sudo: parse error in sudoers

>>> /etc/sudoers.d/deploy: syntax error near line 2 <<<

What it means

visudo (or sudo itself) found a syntax error in a sudoers file. sudo refuses to run with broken rules, so it is broken for everyone until the file is fixed.

Causes, most likely first

  1. A syntax error in any sudoers file (missing colon after NOPASSWD, a typo).

    Check

    visudo -c   # as root, or pkexec visudo -c: names the file and line

    Fix

    Fix the file as root (console, rescue boot, or pkexec), then visudo -c.

  2. A sudoers file with the wrong mode or owner.

    Check

    ls -l /etc/sudoers.d   # not 440 root root

    Fix

    chmod 440 and chown root:root the file, then visudo -c.

Prevent it

Edit only with visudo -f; keep a root session open while testing.

Now learn it: break and fix it yourself in sudo: parse error in sudoers, then take Level 12 · Security hardening. Open in the interactive reference · Link to this answer

Users and security

WARNING: UNPROTECTED PRIVATE KEY FILE!

Permissions 0644 for '/home/ubuntu/.ssh/id_ed25519' are too open.

What it means

ssh refuses to use a private key that other users can read, because then it is not private. 0644 means everyone on the machine can read it.

Cause

  1. The private key is readable by group or others.

    Check

    ls -l ~/.ssh   # -rw-r--r-- on the key: this is your cause

    Fix

    chmod 600 ~/.ssh/id_ed25519, and consider the key exposed if others could log in to this machine.

Prevent it

Copy keys with install -m 600, not cp.

Now learn it: break and fix it yourself in UNPROTECTED PRIVATE KEY FILE, then take Level 12 · Security hardening. Open in the interactive reference · Link to this answer

Users and security

Permission denied (publickey)

deploy@db-01: Permission denied (publickey).

What it means

The server allows only key logins, and none of the keys the client offered was accepted for that user. The client cannot see why; the server's log usually says.

Causes, most likely first

  1. Wrong user for the image: ubuntu on Ubuntu cloud images, rocky on Rocky.

    Check

    ssh -v deploy@db-01 2>&1 | grep 'Authenticating to'   # the user and host you really used

    Fix

    Log in as the image's user first.

  2. The client did not offer the key (wrong file, too-open mode, another key in the agent).

    Check

    ssh -v deploy@db-01 2>&1 | grep -e Offering -e 'Authentications that can continue'   # no Offering line for your key: the client never sent it

    Fix

    Name the key: ssh -i ~/.ssh/id_ed25519, or IdentityFile in ~/.ssh/config; key mode 600.

  3. The key is not in that user's authorized_keys, or StrictModes rejected the file.

    Check

    sudo journalctl -u ssh -n 20   # on the server (sshd on Rocky): bad ownership or modes

    Fix

    Add the public key; fix modes: ~/.ssh 700, authorized_keys 600.

  4. On Rocky/RHEL, authorized_keys has the wrong SELinux label.

    Check

    ls -Z /home/deploy/.ssh/authorized_keys   # not ssh_home_t

    Fix

    sudo restorecon -Rv /home/deploy/.ssh.

  5. The server refuses the key type: old ssh-rsa SHA-1 signatures are off in recent OpenSSH.

    Check

    sudo journalctl -u ssh -n 20   # a refused signature algorithm

    Fix

    Make an ed25519 key rather than re-enabling ssh-rsa on the server.

Prevent it

Distribute keys with configuration management.

Now learn it: break and fix it yourself in UNPROTECTED PRIVATE KEY FILE · Authentication refused: bad ownership or modes, then take Level 12 · Security hardening. Open in the interactive reference · Link to this answer

Users and security

Authentication refused: bad ownership or modes

Authentication refused: bad ownership or modes for directory /home/deploy/.ssh

What it means

This is the server's side of a refused key: sshd (StrictModes) refuses authorized_keys when the home folder, ~/.ssh or the file can be written by someone else, because they could add their own key.

Cause

  1. Home, ~/.ssh or authorized_keys is writable by group or others, or owned by someone else.

    Check

    ls -ld /home/deploy /home/deploy/.ssh   # w for group or others, or another owner
    ls -l /home/deploy/.ssh

    Fix

    chmod 755 home (or 750), 700 ~/.ssh, 600 authorized_keys, all owned by the user.

Prevent it

Never chmod -R a home folder.

Now learn it: break and fix it yourself in Authentication refused: bad ownership or modes, then take Level 12 · Security hardening. Open in the interactive reference · Link to this answer

Users and security

REMOTE HOST IDENTIFICATION HAS CHANGED

WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!

What it means

ssh remembers each server's host key in known_hosts. This server now shows a different key, so ssh stops: either the server was rebuilt, or someone is in the middle of the connection.

Causes, most likely first

  1. The server was rebuilt or reinstalled, or its address now belongs to another machine.

    Check

    ssh-keygen -F db-01   # the key you saved
    ssh-keyscan db-01 | ssh-keygen -lf -   # the key it shows now; compare with the server's console

    Fix

    Once you know the change is expected: ssh-keygen -R db-01 (and its IP address), then connect and accept the new key.

  2. Someone is intercepting the connection: rare, but this is what the check is for.

    Check

    ssh-keyscan db-01 | ssh-keygen -lf -   # differs from the fingerprint on the server's console

    Fix

    Do not connect; investigate the network path.

Prevent it

When you rebuild a server, share its new host key fingerprint; never switch off StrictHostKeyChecking to make the warning go away.

Now learn it: break and fix it yourself in UNPROTECTED PRIVATE KEY FILE, then take Level 12 · Security hardening. Open in the interactive reference · Link to this answer

Users and security

Secrets file readable by everyone

-rw-r--r-- 1 root root /etc/payments/payments.env

What it means

-rw-r--r-- (644) means every user on the machine can read the file. For a file of credentials that is a leak waiting to happen, even if nobody has read it yet.

Causes, most likely first

  1. Created with the default umask, or copied without setting the mode.

    Check

    ls -l /etc/payments/payments.env   # -rw-r--r--
    systemctl show -p User payments   # who needs to read it

    Fix

    sudo chown root:payments FILE && sudo chmod 640 FILE, then restart the service to prove it still reads it.

  2. It is loaded by systemd itself with EnvironmentFile=, so the service user does not need to read it at all.

    Check

    systemctl cat payments | grep -i environmentfile   # it is listed

    Fix

    systemd reads it as root, so root:root mode 600 is enough.

Prevent it

Install secrets with install -m 640 -o root -g SERVICE, or use a secrets manager.

Now learn it: break and fix it yourself in A world-readable secrets file, then take Level 12 · Security hardening. Open in the interactive reference · Link to this answer

Users and security

Internal service listening on 0.0.0.0

LISTEN 0 511 0.0.0.0:6379 0.0.0.0:*

What it means

0.0.0.0:6379 in the Local Address column means Redis accepts connections on every network interface, not only from this machine.

Causes, most likely first

  1. bind 0.0.0.0 in its config.

    Check

    sudo ss -tlnp   # 0.0.0.0 or [::] in Local Address: every interface

    Fix

    Bind to 127.0.0.1 (or the private interface) and restart.

  2. protected-mode or auth turned off, so nothing stops a stranger once they reach it.

    Check

    grep -E '^(bind|protected-mode|requirepass)' /etc/redis/redis.conf   # values only you see; do not paste them

    Fix

    Require auth, keep protected-mode on, and keep a firewall as a second layer.

Prevent it

Review ss -tlnp in every security check; keep a firewall as a second layer.

Now learn it: break and fix it yourself in Redis open to the network, then take Level 12 · Security hardening. Open in the interactive reference · Link to this answer

Users and security

A user lost their groups after usermod

groups=1002(alice),1003(developers)

What it means

usermod -G sets the list of supplementary groups to exactly what you give it. Without -a it removes every group you did not list, so access through docker, sudo and others is gone.

Cause

  1. usermod -G without -a replaced every supplementary group.

    Check

    id alice   # only the groups you just named
    grep alice /etc/group

    Fix

    Add back what was lost: sudo usermod -aG docker,sudo alice.

Prevent it

Always use usermod -aG (or gpasswd -a).

Now learn it: break and fix it yourself in Added to a group, lost the others, then take Level 04 · Users, groups and sudo. Open in the interactive reference · Link to this answer

Users and security

This account is currently not available

This account is currently not available.

What it means

The account's login shell is nologin, which prints this line and exits. Service accounts are made that way on purpose, so nobody logs in as them.

Cause

  1. The account's shell is /usr/sbin/nologin (/sbin/nologin on Rocky).

    Check

    getent passwd www-data   # the last field is the shell

    Fix

    Run the command as that user instead: sudo -u www-data COMMAND, or for a shell once sudo -u www-data bash.

Prevent it

Leave service accounts on nologin, and give people their own accounts.

Now learn it: break and fix it yourself in Added to a group, lost the others, then take Level 04 · Users, groups and sudo. Open in the interactive reference · Link to this answer

Users and security

SSH brute force in auth.log

Failed password for invalid user admin from 203.0.113.77 port 51544 ssh2

What it means

Bots across the internet try common user names and passwords against every reachable sshd. "invalid user admin" means that account does not even exist here. The real question is whether passwords are accepted at all, and whether any attempt succeeded.

Causes, most likely first

  1. Password logins are enabled on an internet-facing sshd.

    Check

    sudo sshd -T | grep -i passwordauthentication   # yes: the value in effect
    sudo grep 'Accepted password' /var/log/auth.log   # /var/log/secure on Rocky; any line: someone got in, treat it as an incident

    Fix

    PasswordAuthentication no, sudo sshd -t, sudo systemctl reload ssh (sshd on Rocky).

  2. A drop-in in sshd_config.d (such as 50-cloud-init.conf) overrides the main file: the first value read wins.

    Check

    grep -ri passwordauthentication /etc/ssh/sshd_config.d/   # a yes that wins

    Fix

    Set it in that drop-in, or in one that sorts first. Add fail2ban: on Ubuntu its sshd jail is on after install; on Rocky it comes from EPEL and the jail must be enabled.

Prevent it

Keys only, no root login, fail2ban, and ideally SSH only from a VPN or bastion.

Now learn it: break and fix it yourself in SSH brute force, then take Level 12 · Security hardening. Open in the interactive reference · Link to this answer

Users and security

SSL certificate problem: certificate has expired

curl: (60) SSL certificate problem: certificate has expired

What it means

The client checked the certificate's "not after" date and it has passed, so it refuses the connection. What matters is the certificate the server actually sends, which may not be the file you renewed.

Causes, most likely first

  1. Renewal stopped: the timer is disabled or renewal fails.

    Check

    sudo certbot certificates   # expired on disk too
    systemctl list-timers --all | grep -i certbot   # missing or never ran

    Fix

    sudo certbot renew && sudo systemctl reload nginx, then enable the timer your install has: certbot.timer (apt), certbot-renew.timer (EPEL on Rocky) or snap.certbot.renew.timer (snap).

  2. Renewed, but the server was not reloaded, or it serves a copy of the old file.

    Check

    echo | openssl s_client -connect shop.example:443 -servername shop.example 2>/dev/null | openssl x509 -noout -enddate   # what clients get
    sudo openssl x509 -enddate -noout -in /etc/letsencrypt/live/shop.example/fullchain.pem   # newer than the served date: this is your cause

    Fix

    Reload the server, and point it at /etc/letsencrypt/live/..., not a copy.

  3. The server sends an expired intermediate certificate.

    Check

    echo | openssl s_client -connect shop.example:443 -servername shop.example -showcerts 2>/dev/null | grep -A1 ' s:'   # the chain it sends

    Fix

    Serve fullchain.pem, not an old chain file.

  4. The client's clock is ahead.

    Check

    timedatectl   # on the client

    Fix

    Sync the client's clock.

Prevent it

Monitor the expiry of what is served (openssl s_client), not just that renewal ran.

Now learn it: break and fix it yourself in certificate has expired, then take Level 12 · Security hardening. Open in the interactive reference · Link to this answer

Boot, kernel and packages

Boot, kernel and packages

Could not get lock /var/lib/dpkg/lock-frontend

E: Could not get lock /var/lib/dpkg/lock-frontend. It is held by process 2810 (unattended-upgr)

What it means

apt takes a lock so that only one package operation runs at a time. Another process holds it, here unattended-upgrades (pid 2810), so this apt waits or stops.

Causes, most likely first

  1. Another apt or dpkg is running: often unattended-upgrades on a new server.

    Check

    ps -fp 2810   # the process holding the lock
    systemctl status unattended-upgrades

    Fix

    Wait for it to finish, then retry. Never delete lock files: the lock goes when its process exits.

  2. cloud-init is still installing packages on first boot.

    Check

    cloud-init status   # running: first-boot setup is not done

    Fix

    cloud-init status --wait, then retry.

Prevent it

In automation, wait for the lock (apt-get -o DPkg::Lock::Timeout=300).

Now learn it: break and fix it yourself in Could not get lock /var/lib/dpkg/lock-frontend, then take Level 13 · Boot, kernel and packages. Open in the interactive reference · Link to this answer

Boot, kernel and packages

dpkg was interrupted

E: dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem.

What it means

dpkg records each install step; the last one did not finish, so packages are half configured. apt refuses to do anything else until that is completed.

Cause

  1. An install was killed, or lock files were removed while it ran.

    Check

    sudo dpkg --audit   # the half-configured packages

    Fix

    sudo dpkg --configure -a, then sudo apt -f install.

Prevent it

Do not kill apt or delete its locks.

Now learn it: break and fix it yourself in Could not get lock /var/lib/dpkg/lock-frontend, then take Level 13 · Boot, kernel and packages. Open in the interactive reference · Link to this answer

Boot, kernel and packages

E: Unable to locate package

E: Unable to locate package nodejs-lts

What it means

apt looked the name up in its package lists and found nothing. The lists are a local copy downloaded by apt update, so the package may exist but not in what this machine knows.

Causes, most likely first

  1. The package lists are old or were never downloaded (a fresh image).

    Check

    apt-cache policy nodejs   # no candidate, or nothing at all

    Fix

    sudo apt update, then install again.

  2. The name is wrong, or different on this release.

    Check

    apt-cache search nodejs   # the real names

    Fix

    Use the real name from apt-cache search.

  3. It lives in a repository you have not added (universe, or the vendor's own).

    Check

    apt-cache policy   # universe or the vendor's repository missing

    Fix

    sudo add-apt-repository universe, or add the vendor's repository.

Prevent it

Start scripts for fresh servers with apt-get update.

Now learn it: break and fix it yourself in Could not get lock /var/lib/dpkg/lock-frontend, then take Level 13 · Boot, kernel and packages. Open in the interactive reference · Link to this answer

Boot, kernel and packages

apt: NO_PUBKEY, repository is not signed

The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 1A2B3C4D5E6F7A8B

What it means

apt checks every repository's signature. It could not, because the key that signed this repository (ID ending 1A2B3C4D5E6F7A8B) is not in the keyring the repository is set to trust.

Causes, most likely first

  1. The repository's signing key is not installed, or the vendor rotated it.

    Check

    gpg --show-keys /etc/apt/keyrings/vendor.gpg   # no key ends with the NO_PUBKEY id

    Fix

    Download the vendor's current key, check its fingerprint against the one they publish, and sudo gpg --dearmor -o /etc/apt/keyrings/vendor.gpg.

  2. The sources line points at a keyring (signed-by=) that is missing or holds the old key.

    Check

    grep -ri signed-by /etc/apt/sources.list /etc/apt/sources.list.d/   # Signed-By: in .sources files
    ls -l /etc/apt/keyrings /usr/share/keyrings   # the file it names is there?

    Fix

    sudo install -d -m 0755 /etc/apt/keyrings, put the key there, and point the line at it: deb [signed-by=/etc/apt/keyrings/vendor.gpg] URL SUITE main, then sudo apt update.

Prevent it

One keyring per repository, named with signed-by=; apt-key is deprecated.

Now learn it: break and fix it yourself in Could not get lock /var/lib/dpkg/lock-frontend, then take Level 13 · Boot, kernel and packages. Open in the interactive reference · Link to this answer

Boot, kernel and packages

Release file is not valid yet

E: Release file for http://archive.ubuntu.com/ubuntu/dists/noble-updates/InRelease is not valid yet (invalid for another 3d 2h 41min 7s). Updates for this repository will not be applied.

What it means

Repository index files carry a date. This machine's clock is earlier than that date, so apt treats the file as from the future and refuses it. "invalid for another 3d" says how far behind the clock is.

Cause

  1. The system clock is in the past (often a restored snapshot or a VM without NTP).

    Check

    timedatectl   # the wrong date, System clock synchronized: no

    Fix

    sudo timedatectl set-ntp true.

Prevent it

Keep NTP on everywhere; snapshots restored to VMs often have old clocks.

Now learn it: break and fix it yourself in Certificate is not yet valid, then take Level 13 · Boot, kernel and packages. Open in the interactive reference · Link to this answer

Boot, kernel and packages

System restart required

*** System restart required ***

What it means

The package manager installed a new kernel or core library that only takes effect after a reboot. Until then the old one runs, so scanners still see it.

Cause

  1. A kernel or libc update only applies after a reboot.

    Check

    uname -r   # the running kernel, older than the installed one
    cat /run/reboot-required.pkgs   # Ubuntu: what asked for it
    sudo dnf needs-restarting -r   # Rocky: whether a reboot is needed

    Fix

    Reboot in a window; check systemctl --failed afterwards.

Prevent it

Schedule reboots (or livepatch) as part of patching.

Now learn it: break and fix it yourself in Patched, but still vulnerable, then take Level 13 · Boot, kernel and packages. Open in the interactive reference · Link to this answer

Boot, kernel and packages

Routing or NAT stops after a reboot

net.ipv4.ip_forward = 0

What it means

net.ipv4.ip_forward = 0 means the kernel will not pass packets from one interface to another. A gateway, VPN or container host needs it on, and it was off again after the reboot.

Causes, most likely first

  1. The setting was changed with sysctl -w and never persisted.

    Check

    grep -r ip_forward /etc/sysctl.conf /etc/sysctl.d   # nothing: this is your cause

    Fix

    echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-forward.conf && sudo sysctl --system.

  2. A file read later sets it back: sysctl.d files apply in name order, and the last value wins.

    Check

    systemd-analyze cat-config sysctl.d | grep -e '^# /' -e ip_forward   # every file in order: the last ip_forward wins

    Fix

    Remove the later line (99-sysctl.conf is /etc/sysctl.conf).

Prevent it

Persist every kernel setting in /etc/sysctl.d, and reboot-test.

Now learn it: break and fix it yourself in The NAT gateway stopped routing, then take Level 13 · Boot, kernel and packages. Open in the interactive reference · Link to this answer

Rocky Linux and RHEL

Rocky Linux and RHEL

dnf: Unable to find a match

Error: Unable to find a match: htop

What it means

dnf searched every enabled repository and found no package by that name. On Rocky many common tools are not in the base repositories.

Causes, most likely first

  1. The package lives in EPEL, which is not enabled.

    Check

    dnf repolist   # no epel

    Fix

    sudo dnf install -y epel-release, then install again.

  2. It needs CRB (PowerTools on Rocky 8), which is off by default.

    Check

    dnf repolist   # epel listed, crb not

    Fix

    sudo dnf config-manager --set-enabled crb (powertools on Rocky 8).

  3. A typo, or a package name that differs from Debian's.

    Check

    dnf search htop
    dnf provides '*/htop'   # the package that ships the file

    Fix

    Install the name dnf shows.

  4. An exclude= line in dnf.conf or a repo file hides it.

    Check

    grep -r exclude /etc/dnf/dnf.conf /etc/yum.repos.d/

    Fix

    Change it only once you know why it was added.

Prevent it

Enable the repositories you rely on in the server image.

Now learn it: break and fix it yourself in dnf: Unable to find a match, then take Level 16 · Rocky Linux and RHEL. Open in the interactive reference · Link to this answer

Rocky Linux and RHEL

SELinux denial: 403 with correct permissions

avc:  denied  { read } for  pid=1312 comm="nginx" name="index.html" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0

What it means

An AVC denial is SELinux saying no. Read it as: process nginx (domain httpd_t, in scontext) tried to read a file labelled user_home_t (tcontext), and the policy does not allow that. permissive=0 means it was really blocked. File modes are not involved.

Causes, most likely first

  1. Files carry the wrong SELinux label. mv keeps the label from where the file was, so files moved from a home folder stay user_home_t.

    Check

    ls -Z /srv/shop   # user_home_t, not httpd_sys_content_t
    sudo ausearch -m avc -ts recent | audit2why   # says a label fixes it

    Fix

    sudo restorecon -Rv /srv/shop if the path has a default; otherwise see the next cause.

  2. A path outside the policy's defaults (/srv, /data), so restorecon has no web label to apply.

    Check

    matchpathcon /srv/shop   # var_t or default_t

    Fix

    sudo semanage fcontext -a -t httpd_sys_content_t '/srv/shop(/.*)?' && sudo restorecon -Rv /srv/shop.

  3. A boolean is off, such as httpd_can_network_connect for nginx proxying to a backend (a 502).

    Check

    sudo ausearch -m avc -ts recent | audit2why   # names the boolean (policycoreutils-python-utils)

    Fix

    sudo setsebool -P httpd_can_network_connect 1. Never setenforce 0, and do not feed denials to audit2allow blindly: it allows whatever was denied.

Prevent it

Never setenforce 0 as a fix; use cp (not mv) into labelled places, or define fcontext rules for custom paths.

Now learn it: break and fix it yourself in 403 with correct permissions: SELinux, then take Level 16 · Rocky Linux and RHEL. Open in the interactive reference · Link to this answer

Rocky Linux and RHEL

bind() failed (13: Permission denied) on a custom port

nginx: [emerg] bind() to 0.0.0.0:8090 failed (13: Permission denied)

What it means

nginx tried to listen on port 8090 and the kernel said no. The master process runs as root, so it is not a normal permission: on Rocky/RHEL, SELinux lets the web server bind only ports labelled http_port_t.

Causes, most likely first

  1. SELinux allows httpd_t to bind only http_port_t ports, and 8090 is not one.

    Check

    sudo ausearch -m avc -ts recent   # name_bind denied: SELinux
    sudo semanage port -l | grep -w 8090   # not listed, or under another type

    Fix

    sudo semanage port -a -t http_port_t -p tcp 8090 (semanage is in policycoreutils-python-utils). If it says the port is already defined, use -m instead of -a.

  2. A non-root process on a port below 1024 (any distribution).

    Check

    ps -o user= -p $(pgrep -o nginx)   # not root

    Fix

    Use a port above 1024 behind a proxy, or AmbientCapabilities=CAP_NET_BIND_SERVICE in the unit.

Prevent it

Include port labels in provisioning when you use non-standard ports.

Now learn it: break and fix it yourself in bind() failed (13: Permission denied), then take Level 16 · Rocky Linux and RHEL. Open in the interactive reference · Link to this answer

Rocky Linux and RHEL

firewalld drops traffic to a new port

curl: (7) Failed to connect to app-01 port 8080: No route to host

What it means

Rocky's firewall, firewalld, rejects connections to ports that are not open in the zone, and the client sees "No route to host" at once. The service is fine; local curl works because the firewall does not filter the machine's own loopback traffic.

Causes, most likely first

  1. The port is not open in the zone the interface is in.

    Check

    sudo firewall-cmd --get-active-zones   # the zone the interface is in
    sudo firewall-cmd --zone=public --list-all   # 8080/tcp not in ports or services

    Fix

    sudo firewall-cmd --permanent --zone=public --add-port=8080/tcp && sudo firewall-cmd --reload. For a known service, --add-service=http reads better.

  2. Opened at runtime only, lost after --reload or reboot.

    Check

    sudo firewall-cmd --permanent --list-ports   # missing here while --list-ports shows it

    Fix

    sudo firewall-cmd --runtime-to-permanent.

  3. Open on the host, but a cloud security group drops it: that times out instead of No route to host.

    Check

    sudo tcpdump -ni any tcp port 8080   # no SYN arrives

    Fix

    Allow the port from the client's subnet in the security group. Do not stop firewalld to make it work.

Prevent it

Always use --permanent plus --reload (or --runtime-to-permanent).

Now learn it: break and fix it yourself in firewalld blocks the new port, then take Level 16 · Rocky Linux and RHEL. Open in the interactive reference · Link to this answer

Rocky Linux and RHEL

resize2fs: Bad magic number in super-block

resize2fs: Bad magic number in super-block while trying to open /dev/mapper/rl-root

What it means

resize2fs only understands ext2/3/4. It read the start of the volume, did not find an ext4 superblock (the "magic number"), and stopped. The volume is fine; it is another file system.

Cause

  1. The file system is XFS, Rocky's default, not ext4.

    Check

    df -hT /   # xfs

    Fix

    sudo xfs_growfs /. Next time use lvextend -r, which picks the right tool.

Prevent it

Use lvextend -r, which picks the right tool for the file system.

Now learn it: break and fix it yourself in Grow / on LVM and XFS, then take Level 16 · Rocky Linux and RHEL. Open in the interactive reference · Link to this answer

Rocky Linux and RHEL

lvextend: Insufficient free space

Insufficient free space: 10240 extents needed, but only 7423 available

What it means

LVM sizes are counted in extents (blocks of usually 4 MiB). You asked for 10240 more, and the volume group has only 7423 free.

Cause

  1. The volume group has less free space than requested.

    Check

    sudo vgs   # VFree
    sudo pvs   # which disks have room

    Fix

    Ask for less (-l +100%FREE), or add a disk: pvcreate, vgextend, then lvextend -r.

Prevent it

Watch VFree; plan growth per volume.

Now learn it: break and fix it yourself in Grow / on LVM and XFS, then take Level 16 · Rocky Linux and RHEL. Open in the interactive reference · Link to this answer

Performance

Performance

High load, idle CPU: NFS server not responding

nfs: server nfs-02 not responding, still trying

What it means

The kernel's NFS client cannot reach the server and, for a hard mount, keeps retrying forever. Every process that touches the mount waits in D state (uninterruptible), which counts in the load average, so load climbs while CPUs sit idle.

Cause

  1. A hard NFS mount whose server is down or unreachable.

    Check

    sudo dmesg | grep -i nfs   # not responding, still trying
    ps -eo pid,stat,cmd | grep ' D'   # processes stuck on the mount
    df -hl   # skips network mounts, so it does not hang

    Fix

    Fix the server or the network; to free the client, sudo umount -f -l /mnt/reports, then fix or remove its fstab line.

Prevent it

Mount non-essential shares with _netdev,nofail and x-systemd.automount; use df -l in scripts and monitoring.

Now learn it: break and fix it yourself in High load, idle CPU: a hung NFS mount, then take Level 14 · Performance. Open in the interactive reference · Link to this answer

Performance

Memory that only grows

queue-worker   2.40G RSS, +120 MB/min

What it means

RSS is the memory a process really holds. It climbs steadily and never comes down, until the machine swaps or the OOM killer acts.

Causes, most likely first

  1. A leak in the program.

    Check

    ps -o pid,rss,etime,cmd -p PID; sleep 60; ps -o pid,rss,etime,cmd -p PID   # RSS up every time

    Fix

    Contain it: sudo systemctl set-property NAME MemoryMax=1G with Restart=always, and report the leak with the numbers.

  2. Not a leak: a heap or cache that grows to its cap (a JVM up to -Xmx) and then stays flat.

    Check

    ps aux --sort=-rss | head   # watch for an hour: it levels off

    Fix

    Set the cap to fit the machine.

Prevent it

MemoryMax= on every service; alert on RSS growth, not only on free memory.

Now learn it: break and fix it yourself in A slow memory leak, then take Level 14 · Performance. Open in the interactive reference · Link to this answer

Performance

swapon: read swap header failed

swapon: /swapfile: read swap header failed

What it means

swapon read the start of the file looking for a swap signature and found none. A file becomes usable as swap only after mkswap writes that header.

Cause

  1. mkswap was not run on the file.

    Check

    file /swapfile   # data, not swap file

    Fix

    sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile, then a line in /etc/fstab.

Prevent it

Script swap creation: fallocate, chmod 600, mkswap, swapon, fstab.

Now learn it: break and fix it yourself in No swap, and memory is tight, then take Level 14 · Performance. Open in the interactive reference · Link to this answer

Performance

swapon: insecure permissions, 0600 suggested

swapon: /swapfile: insecure permissions 0644, 0600 suggested.

What it means

Swap holds pages of memory, secrets included. swapon warns that other users can read this file and suggests 0600. It still works, but anyone could read memory contents.

Cause

  1. The swap file is readable by others.

    Check

    ls -l /swapfile   # -rw-r--r--

    Fix

    sudo chmod 600 /swapfile.

Prevent it

chmod 600 before mkswap.

Now learn it: break and fix it yourself in No swap, and memory is tight, then take Level 14 · Performance. Open in the interactive reference · Link to this answer

Performance

High load, idle CPU (I/O wait)

%Cpu(s):  5.6 us,  1.4 sy,  0.0 ni,  0.0 id, 81.0 wa

What it means

wa in top is the share of time CPUs sit idle waiting for disk. Processes waiting on I/O are in D state and count in the load, so load is high while the CPUs do almost nothing.

Causes, most likely first

  1. A saturated disk: a backup, rsync or updatedb is reading or writing as fast as it can.

    Check

    iostat -x 1 5   # %util near 100 and a high await (sysstat)
    sudo iotop -o   # which process

    Fix

    sudo ionice -c3 -p PID for the I/O hog, or reschedule it.

  2. A cloud volume at its IOPS or throughput limit (burst credits used up).

    Check

    iostat -x 1 5   # flat r/s+w/s at the volume's limit with modest %util

    Fix

    Raise the volume's IOPS or throughput, or move the load.

  3. A hung network mount.

    Check

    ps -eo pid,stat,cmd | grep ' D'   # stuck on an NFS path

    Fix

    See NFS server not responding.

Prevent it

Run backups with ionice -c3 nice -n19, off-peak, or on a separate disk.

Now learn it: break and fix it yourself in High load, idle CPU, then take Level 14 · Performance. Open in the interactive reference · Link to this answer

Performance

Swap thrashing

si 1840  so 2210

What it means

si and so in vmstat are pages swapped in and out per second. Both high at once means the working set does not fit in RAM: the kernel keeps moving pages to disk and back, and everything waits on that.

Cause

  1. The working set does not fit in RAM.

    Check

    vmstat 1 5   # si and so both high
    free -h   # little available
    top -o %MEM   # the biggest user

    Fix

    Cap the hog: sudo systemctl set-property NAME MemoryMax=1G, or stop it; add RAM if the load is real.

Prevent it

Set MemoryMax= per service; alert on swap-in rate.

Now learn it: break and fix it yourself in Swap thrashing, then take Level 14 · Performance. Open in the interactive reference · Link to this answer

Performance

Main process exited, code=killed, status=9/KILL

reports.service: Failed with result 'oom-kill'.

What it means

systemd says the service's main process died from signal 9 (SIGKILL), and Result says who decided: oom-kill means memory. The service then restarts and dies again.

Causes, most likely first

  1. The kernel OOM killer or a MemoryMax= limit.

    Check

    systemctl show reports -p Result -p NRestarts   # Result=oom-kill
    sudo journalctl -k | grep -i oom

    Fix

    Fit the service to memory (heap, workers), or give it more.

  2. systemd-oomd under memory pressure, where it is enabled.

    Check

    journalctl -u systemd-oomd   # Killed ... due to memory pressure

    Fix

    Lower the memory use, or tune oomd for that slice.

  3. A stop that ran past TimeoutStopSec=: systemd sends SIGKILL.

    Check

    systemctl show reports -p Result   # Result=timeout

    Fix

    Find why stopping hangs before raising TimeoutStopSec=.

  4. Someone's kill -9.

    Check

    systemctl show reports -p Result   # Result=signal
    journalctl -u reports | grep -i kill

    Fix

    Find who sent it.

Prevent it

Never protect one service with oom_score_adj at the cost of the database.

Now learn it: break and fix it yourself in Killed by the OOM killer, then take Level 14 · Performance. Open in the interactive reference · Link to this answer

Performance

Too many open files

accept4() failed (24: Too many open files)

What it means

Every open file and network connection uses a file descriptor, and each process has a limit. The service reached it (EMFILE, code 24), so new connections or files fail.

Causes, most likely first

  1. The service's soft open-files limit (1024 by default) is too low for its load.

    Check

    cat /proc/$(pgrep -o gateway)/limits | grep 'open files'   # the limit the process really has
    ls /proc/$(pgrep -o gateway)/fd | wc -l   # at the limit

    Fix

    Drop-in [Service] LimitNOFILE=65535, daemon-reload, restart.

  2. A file descriptor leak.

    Check

    ls /proc/$(pgrep -o gateway)/fd | wc -l   # keeps climbing at low traffic: a leak

    Fix

    Fix the program; a bigger limit only delays it.

  3. The program's own cap, such as nginx worker_rlimit_nofile.

    Check

    grep -r worker_rlimit_nofile /etc/nginx/

    Fix

    Raise that setting too.

Prevent it

limits.conf does not apply to services: set LimitNOFILE= in the unit.

Now learn it: break and fix it yourself in Too many open files, then take Level 14 · Performance. Open in the interactive reference · Link to this answer