Linux interview questions and answers for DevOps and SRE

26 Linux questions from real DevOps and SRE interviews, sorted by seniority: permissions, processes and signals, systemd, networking, and the live debugging you get asked to narrate.

26 questions7 junior11 mid8 seniorLive exerciseWhat each level testsHow to prepare

All 26 questions

  1. 1

    Explain what chmod 644 and chmod 755 actually mean.

    Each digit is a three-bit field for one class of user: owner, group, other. Read is 4, write is 2, execute is 1.

    644 is owner read and write, group read, other read. That is the normal mode for a file.

    755 is owner read, write and execute, group read and execute, other read and execute. That is the normal mode for a directory or a script.

    The part that shows understanding: on a directory, the bits mean something different. Read lets you list names, write lets you create and delete entries, and execute lets you traverse into it. A directory with read but not execute gives you the file names and nothing else, which is why 755 rather than 644 on directories is not arbitrary.

    link
  2. 2

    What is the difference between a hard link and a symbolic link in Linux?

    A hard link and a symbolic link comparedTwo directory entries, file.txt and hard.txt, both point at the same inode, which points at the data blocks. A third entry, soft.txt, is itself a small file containing the path text file.txt, so it resolves through the name rather than to the inode.DIRECTORY ENTRIESfile.txthard.txtsoft.txtcontains "file.txt"inode 4021metadataData blocksresolves by namelink count 2
    A hard link is another name for the same inode, so it keeps working when the original name is removed. A symlink stores a path, so removing the original leaves it dangling.
    Hard linkSymbolic link
    Points atThe same inodeA path, stored as text
    Crosses filesystemsNoYes
    Can target a directoryNoYes
    If the original is deletedStill worksDangles
    Costs an inodeNo, shares oneYes, it is a small file
    ln file.txt hard.txt      # same inode
    ln -s file.txt soft.txt   # contains the path "file.txt"
    ls -li                    # first column is the inode number

    The practical consequence is that deleting the original leaves a hard link fully working and leaves a symlink dangling.

    link
  3. 3

    How do you find which process is using a port in Linux?

    sudo ss -tlnp | grep :8080

    ss is the current tool; netstat is deprecated and often not installed. The flags are worth knowing: t for TCP, l for listening, n for numeric so it does not do DNS lookups, p for the owning process, which needs root to see processes you do not own.

    sudo lsof -i :8080 gives the same answer with more detail about the open files.

    A good follow-up answer notes that a port held by a process in TIME_WAIT has no owner to show, and that inside a container ss sees only that container's network namespace, so a conflict on the host will not appear there.

    link
  4. 4

    On Linux the disk is full but deleting files does not free space. Why?

    Two usual causes.

    A deleted file still held open by a process. Unlinking removes the name, but the kernel keeps the data until the last file descriptor closes. A log file rotated out from under a running service is the classic case.

    sudo lsof +L1

    That lists open files with a link count of zero. Restarting the holding process, or truncating through its descriptor, releases the space.

    Exhausted inodes rather than bytes. Millions of tiny files fill the inode table while df -h still shows free space.

    df -h /var
    df -i /var

    If df -i shows 100% use, you need to delete files rather than free bytes, and no amount of removing large files will help.

    link
  5. 5

    What is the difference between > and >> in bash, and how do you redirect stderr?

    > truncates the target and writes; >> appends.

    Streams are numbered: 0 is stdin, 1 is stdout, 2 is stderr. So:

    cmd > out.log          # stdout only, truncating
    cmd 2> err.log         # stderr only
    cmd > all.log 2>&1     # both into one file
    cmd &> all.log         # same thing, bash shorthand
    cmd 2>&1 | grep error  # send stderr through the pipe too

    Order matters in > all.log 2>&1: stdout is pointed at the file first, then stderr is pointed at wherever stdout currently goes. Writing 2>&1 > all.log sends stderr to the terminal and only stdout to the file, which is a real and common mistake.

    link
  6. 6

    What is the difference between SIGTERM, SIGKILL and SIGHUP?

    SignalNumberCatchableMeans
    SIGTERM15YesStop when you are ready. What kill sends by default
    SIGKILL9NoThe kernel removes the process now, with no chance to flush or close
    SIGHUP1YesOriginally "the terminal hung up". Daemons repurpose it as "reload your config"

    The interview point: reaching for kill -9 first is a habit worth arguing against. It risks corrupting state, and a process that ignores SIGTERM is a bug to investigate. Note also that a process stuck in uninterruptible sleep, state D, will not die even from SIGKILL, because it is blocked in the kernel waiting on IO.

    link
  7. 7

    A Linux server is slow. Walk me through diagnosing it.

    Work outward from load, and name what each step rules out.

    uptime                  # load average over 1, 5, 15 minutes

    Load higher than the core count means processes are waiting. Compare the three numbers to see whether it is rising or recovering.

    top -b -n1 | head -20

    Read the CPU line rather than just the process list:

    FieldHigh meansLook at
    %usApplication workThe process list, and the application itself
    %syKernel workSyscall or context-switch churn, vmstat
    %waIO waitDisk, with iostat -xz for await and utilisation
    %stSteal timeA noisy neighbour on shared hardware. Nothing local will fix it
    free -h                 # is it actually memory
    iostat -xz 1 3          # await and %util per device
    ss -s                   # socket totals, are we out of connections
    dmesg -T | tail -30     # OOM kills and hardware errors

    The strongest answers say what they expect to find before running each command, and check dmesg for OOM kills early, since "slow" is often a service being repeatedly killed and restarted.

    link
  8. 8

    What do setuid, setgid and the sticky bit do in Linux?

    BitOn a fileOn a directoryShows as
    setuidRuns as its owner, which is how passwd writes to /etc/shadowNo effectrws------
    setgidRuns as its groupNew files inherit the directory's grouprwxrws---
    stickyNo effectOnly a file's owner may delete it, which is why /tmp is safedrwxrwxrwt
    chmod u+s binary     # setuid,  shows as rws------
    chmod g+s shared/    # setgid
    chmod +t /tmp        # sticky,  shows as drwxrwxrwt

    Add the security note: setuid root binaries are a standard privilege escalation route, and auditing for unexpected ones with find / -perm -4000 is a real hardening step.

    link
  9. 9

    How do you investigate a systemd service that will not start?

    systemctl status myapp
    journalctl -u myapp -n 100 --no-pager

    status gives the current state, the exit code and the last few log lines; journalctl gives the history, which is what you need when it is crash-looping and status only shows the newest attempt. Add -b to limit to this boot, or -f to follow.

    Then narrow by cause. A non-zero exit code with nothing in the journal usually means the unit file is wrong rather than the application: a bad ExecStart path, a WorkingDirectory that does not exist, or a User that cannot read its own files.

    systemd-analyze verify /etc/systemd/system/myapp.service
    systemctl cat myapp        # the effective unit, including drop-ins

    systemctl cat is the one people miss. A drop-in under /etc/systemd/system/myapp.service.d/ overrides the unit you are reading, and this shows what is actually in effect. And after editing any unit, systemctl daemon-reload is required or systemd keeps using the old one.

    link
  10. 10

    A Linux service cannot reach another host. How do you narrow it down?

    Go up the stack, one layer at a time, so each step eliminates something.

    ip addr; ip route          # do we have an address and a route
    ping -c3 10.0.1.20         # layer 3, if ICMP is allowed
    getent hosts api.internal  # is it name resolution
    nc -zv api.internal 8080   # is the specific port reachable
    curl -sv http://api.internal:8080/healthz

    getent hosts is better than nslookup here because it uses the same resolution path the application does, including /etc/hosts and nsswitch, rather than querying DNS directly.

    If the port is refused rather than timing out, something is there and actively saying no, which usually means the service is not listening or is bound to 127.0.0.1. A timeout points at a firewall or security group silently dropping packets.

    sudo ss -tlnp | grep 8080  # on the far end, is it 0.0.0.0 or 127.0.0.1
    link
  11. 11

    Using a shell pipeline, how would you find the ten IPs making the most requests in an access log?

    awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10

    Be able to explain each stage: awk takes the first field, sort groups identical lines together because uniq only collapses adjacent duplicates, uniq -c counts each group, sort -rn orders by that count numerically and descending, and head takes the top ten.

    The mistake interviewers watch for is omitting the first sort, which makes uniq silently undercount.

    A stronger answer scales it. On a very large file, sort spills to disk, and doing the counting in awk avoids that entirely:

    awk '{c[$1]++} END {for (i in c) print c[i], i}' access.log | sort -rn | head -10
    link
  12. 12

    What is a zombie process in Linux, and is it a problem?

    A zombie is a process that has exited but whose parent has not yet read its exit status, so the kernel keeps the process table entry. It shows as state Z and uses no memory or CPU.

    A few transient zombies are normal. A growing pile is a bug in the parent, which is not calling wait(). The real risk is exhausting the process table, since each zombie holds a PID.

    You cannot kill a zombie, because it is already dead. Killing or restarting the parent clears them, and if the parent dies they are reparented to PID 1, which reaps them.

    ps -eo pid,ppid,stat,cmd | awk '$3 ~ /^Z/'

    The container connection is worth raising: an application running as PID 1 in a container inherits the duty of reaping orphans, and most application runtimes do not do it. That is exactly what docker run --init and tini are for.

    link
  13. 13

    What does an inode hold, and what does it not?

    An inode holds a file's metadata: type, permissions, owner and group, size, timestamps, link count, and pointers to the data blocks.

    It does not hold the filename. Names live in directory entries, which map a name to an inode number. That single fact explains several behaviours: hard links are two names for one inode, renaming a file does not touch the inode, and you can delete a file you cannot read as long as you have write permission on the directory.

    ls -li file.txt     # inode number
    stat file.txt       # everything the inode holds
    df -i               # inode usage per filesystem

    It also explains running out of inodes with free disk space, since the table is sized at filesystem creation on ext4.

    link
  14. 14

    What does systemd give you over an init script, and what are the trade-offs?

    Concretely: dependency-ordered parallel startup rather than sequential runlevels, supervision with configurable restart policies, cgroup-based resource limits per unit, socket and timer activation, a structured and indexed journal, and a consistent declarative unit format instead of a few hundred lines of bespoke shell per service.

    [Service]
    ExecStart=/usr/local/bin/myapp
    Restart=on-failure
    RestartSec=5
    MemoryMax=512M
    User=myapp

    The cgroup point matters for a senior answer: systemd puts each unit in its own cgroup, so a restart reliably kills every child process, which an init script's PID file approach frequently failed to do.

    The honest trade-offs: it is large and does a lot beyond init, which is the substance of the long-running criticism; binary journals need journalctl rather than grep; and the behaviour is genuinely complex, so debugging ordering problems means understanding Wants, Requires, After and BindsTo rather than reading a script top to bottom.

    link
  15. 15

    What is Linux load average actually measuring, and how do you interpret it?

    On Linux it is the exponentially-damped average number of processes in runnable or uninterruptible state over one, five and fifteen minutes. The second half is the part people miss: Linux counts processes blocked on IO, which other Unixes do not.

    So a load of 8 on an 8-core box can mean fully utilised CPU, or it can mean eight processes all blocked on a failing disk while the CPU sits idle. Load alone does not distinguish them, which is why it is a starting signal and not a diagnosis.

    uptime
    vmstat 1 5     # r is runnable, b is blocked

    Compare load to core count from nproc, and read the three intervals as a trend: 1-minute well above 15-minute means something is starting now.

    The senior addition is pressure stall information, which answers the question load average cannot:

    cat /proc/pressure/cpu /proc/pressure/io /proc/pressure/memory
    link
  16. 16

    How does the Linux OOM killer decide what to kill, and how do you investigate afterwards?

    When the kernel cannot reclaim enough memory it scores every process, roughly by memory footprint adjusted by oom_score_adj, and kills the highest scorer to free the most memory for the least number of kills. It is not the process that requested the allocation, and it is not the oldest, which is why the victim is often a database rather than the leaking process.

    dmesg -T | grep -i "killed process"
    journalctl -k | grep -i oom
    cat /proc/<pid>/oom_score

    Protect the processes that matter by biasing the score, from -1000 to 1000:

    echo -500 > /proc/<pid>/oom_score_adj

    Two points that mark experience. In a container, hitting the cgroup memory limit produces an OOM kill of that container's process even though the host has memory free, which is the exit code 137 you see in Kubernetes. And overcommit settings matter: with the default heuristic the kernel allows more allocation than it can back, so the reckoning arrives at first touch rather than at allocation.

    link
  17. 17

    What happens, step by step, when you curl https://example.com from a Linux host?

    The point of the question is breadth, so move steadily and do not skip layers.

    1. 1Resolution. The stub resolver consults nsswitch, so /etc/hosts then DNS. A recursive resolver walks root, TLD and authoritative nameservers unless it is cached.
    2. 2Routing. The kernel picks a route and a source address, then resolves the next hop's MAC with ARP.
    3. 3TCP. SYN, SYN-ACK, ACK.
    4. 4TLS. ClientHello with SNI so a shared host knows which certificate to send, then ServerHello, certificate, chain validation against the local trust store including expiry and hostname, key agreement, Finished. TLS 1.3 needs one round trip, not two.
    5. 5HTTP. The request is sent, the server responds, and the connection may be reused for keep-alive.

    Strong answers note that failures are diagnosable by layer, and that curl -v shows you exactly where it stopped. They also mention that the certificate is validated against the hostname, which is why an IP address in the URL fails even when the host is reachable.

    link
  18. 18

    Why does set -euo pipefail matter in a bash script, and where does it still not protect you?

    By default a shell script continues after a failing command and exits 0, which means a broken deploy script reports success.

    set -e exits on an unhandled non-zero status. set -u errors on an unset variable, which catches the rm -rf "$PREFIX/" typo where PREFIX was never set. set -o pipefail makes a pipeline fail if any stage fails, not just the last, so curl bad-url | jq . stops being a success.

    Where it still does not protect you:

    • set -e is ignored in a condition, so if cmd; then is fine, which is intended, but it also does not fire inside && chains the way people expect.
    • A failure in a command substitution used in an assignment can be swallowed.
    • It does nothing for a command that succeeds while doing the wrong thing.

    So the fuller habit is set -euo pipefail, quote every expansion, use "${VAR:?message}" where a value is mandatory, add a trap for cleanup, and run shellcheck in CI.

    link
  19. 19

    How would you find what is consuming disk space on a full Linux server?

    Establish whether it is bytes or inodes first, because the remedy differs:

    df -h; df -i

    Then descend by size rather than guessing:

    sudo du -xh --max-depth=1 / 2>/dev/null | sort -rh | head

    -x keeps it on one filesystem, which stops you walking into network mounts and container overlays and wasting minutes.

    Then check the three usual culprits: /var/log for logs that are not rotating, /var/lib/docker on a container host, and deleted-but-open files, which du cannot see at all:

    sudo lsof +L1

    The operational point worth making: on a full production disk, truncating a live log with : > /var/log/app.log is safer than deleting it, because deleting leaves the space held by the writing process and rotation will not recover it until restart.

    link
  20. 20

    What is the difference between a hard and soft ulimit, and when does it bite?

    Soft limitHard limit
    What it isThe value currently enforcedThe ceiling
    Who can raise itThe process itself, up to the hard limitOnly root
    Read withulimit -Snulimit -Hn
    ulimit -Sn    # soft open file descriptors
    ulimit -Hn    # hard

    The one that bites in production is nofile. Every socket is a file descriptor, so a busy server hits the default 1024 and starts failing with "too many open files" while looking otherwise healthy. It presents as intermittent connection failures under load, not as a clear resource error.

    Raising it in your shell does nothing for a service started by systemd, which is the trap:

    [Service]
    LimitNOFILE=65535

    Check what a running process actually has, rather than what you think you set:

    cat /proc/<pid>/limits
    link
  21. 21

    What is the difference between $?, $$ and $! in bash?

    VariableHoldsTypically used for
    $?Exit status of the last foreground commandBranching on success or failure
    $$PID of the current shellBuilding a unique temporary filename
    $!PID of the last background commandwaiting for it, or killing it later
    grep -q pattern file
    echo "exit status: $?"
    
    long_task &
    pid=$!
    wait "$pid"

    Worth adding: $? is overwritten by every command, including echo, so capture it into a variable immediately if you need it twice.

    link
  22. 22

    How do you read journalctl logs for a service that has already restarted several times?

    journalctl keeps history, which is the advantage over tailing a file that may have been rotated.

    journalctl -u myapp --since "1 hour ago" --no-pager
    journalctl -u myapp -p err          # priority error and above
    journalctl -u myapp -b -1           # the previous boot
    journalctl -u myapp -o json-pretty  # structured fields

    -p err is the one that saves time on a noisy service. -o json-pretty shows the fields you can then filter on directly, which is how you find all messages from one PID across restarts.

    The gotcha to mention: the journal is only persistent if it is configured to be. With Storage=volatile or no /var/log/journal directory, everything is in memory and disappears on reboot, which is a genuinely unpleasant discovery during an incident.

    journalctl --disk-usage
    link
  23. 23

    How do you give a Linux user one privileged capability without giving them root?

    In order of preference.

    A targeted sudo rule. Specific commands, no wildcards that allow shell escapes:

    deploy ALL=(root) NOPASSWD: /bin/systemctl restart myapp

    Be alert that many commands allow escaping to a shell, so granting sudo vim or sudo find is effectively granting root.

    Linux capabilities, when the need is a specific kernel privilege rather than a command. Binding a low port is the standard example:

    sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/myapp

    That replaces the old pattern of starting as root and dropping privileges.

    Group membership, where a group already governs the resource. With the caveat worth stating in any interview: adding someone to the docker group is equivalent to giving them root, because they can mount the host filesystem into a privileged container.

    systemd unit properties such as AmbientCapabilities, so the service gets the privilege without any human having it.

    link
  24. 24

    What is the difference between /etc/hosts, DNS and nsswitch on Linux?

    /etc/hosts is a local static file mapping names to addresses. DNS is the distributed lookup system. /etc/nsswitch.conf is what decides the order in which they are consulted.

    hosts: files dns myhostname

    files means /etc/hosts is checked first, which is why an entry there overrides DNS entirely and is a common cause of "it resolves differently on that one box".

    This is why getent hosts name is the right diagnostic rather than dig name. dig queries DNS directly and bypasses nsswitch, so it can return the correct record while the application still resolves something else from /etc/hosts or a local caching resolver such as systemd-resolved on 127.0.0.53.

    link
  25. 25

    On a Linux server, requests are intermittently slow but CPU, memory and disk all look fine. Where do you look?

    This is usually a queue or a limit rather than a resource, and the interesting answers go to the network stack and to contention.

    Connection and socket limits first. Accept queue overflows and conntrack table exhaustion both produce exactly this symptom:

    ss -s
    netstat -s | grep -i -E "listen|retrans"   # overflows and retransmits
    sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max

    Retransmits point at packet loss somewhere in the path. A non-zero and growing "times the listen queue of a socket overflowed" means the application is not accepting fast enough.

    Then contention that does not show as utilisation: file descriptor limits near the ceiling, a database connection pool fully checked out, garbage collection pauses inside the runtime, DNS resolution timing out on a stale resolver, and steal time on a shared host.

    cat /proc/pressure/io
    vmstat 1 10     # cs for context switches

    The framing worth offering: "fine on averages" often means bad at the tail, so look at p99 rather than means, and check whether slowness correlates with a specific instance, upstream or time pattern before tuning anything.

    link
  26. 26

    How do you grep for a string across a directory tree in Linux, excluding some files?

    grep -rn --include='*.py' --exclude-dir='.git' "TODO" .

    -r recurses, -n gives line numbers, --include and --exclude-dir narrow the search, which matters both for speed and for not matching your own node_modules.

    Useful variations: -i for case-insensitive, -l to list only filenames, -c to count matches, -w for whole words to stop cat matching concatenate, and -A3 -B3 for context lines around each hit.

    Worth mentioning ripgrep if it is available, since it respects .gitignore by default and is substantially faster on a large tree:

    rg -t py TODO
    link

Live exercise: the server is slow

The most frequently asked Linux scenario. Each step should eliminate a class of cause, and saying what you expect to see before you run a command is what separates a strong answer.

  1. Is load average above the core count?

    uptime; nproc
    yes
    Processes are queuing. Compare the 1, 5 and 15 minute figures to see whether it is building or recovering, then find out what they are waiting on.
    no
    It is not a capacity problem. Move straight to the application, its dependencies, and the network rather than tuning the host.
  2. Where is the CPU time going?

    top -b -n1 | head -5
    yes
    High %wa is IO wait, so check iostat -xz 1 3 for per-device await and utilisation. High %sy is kernel work, usually syscall or context-switch churn.
    no
    High %st is steal time, meaning a noisy neighbour on shared hardware. Nothing you tune locally will fix it, and that is the answer.
  3. Is anything being killed for memory?

    dmesg -T | grep -i 'killed process'
    yes
    The slowness is a service being killed and restarted. Memory is the cause and the rest is a symptom, so stop here.
    no
    Memory is not the cause. Confirm with free -h that available memory is genuinely healthy rather than just cached.
  4. Are sockets or file descriptors near a limit?

    ss -s; cat /proc/$(pgrep -f myapp | head -1)/limits
    yes
    This is a queue or a limit rather than a resource, which is exactly the case where everything looks fine on averages. Check the listen queue overflow counter and the conntrack table too.
    no
    Look at the tail rather than the mean: p99 latency, one bad instance, or a slow dependency. Correlate with deploys and with time of day before changing anything.

What each level is testing

  1. 1

    Junior7 questions

    Whether you are comfortable in a shell. Permission bits, links, redirection, finding what holds a port, and why deleting files can fail to free space. Be able to explain the digits in chmod 755, not just type it.

  2. 2

    Mid11 questions

    Whether you have run something in production. Signals and the difference between SIGTERM and SIGKILL, systemd units and the journal, narrowing a network problem layer by layer, and text processing with a pipeline you can explain stage by stage.

  3. 3

    Senior8 questions

    Whether you can debug a system you did not build. What load average actually counts, how the OOM killer chooses, descriptor and conntrack limits, and the intermittent-slowness case where every obvious resource looks fine. Expect open questions with no single right answer.

What the round is like

Linux rounds for DevOps and SRE roles are less about commands than about whether you can reason about a running system. Expect permissions and filesystem questions early, then processes and signals, then a scenario you have to narrate: a full disk, a slow server, a service that will not start. The commands matter less than saying what each one rules out.

How to prepare with this

  1. 1Practise narrating the slow-server flow below until it is automatic. It is the most common live Linux exercise and the sequence matters more than the commands.
  2. 2For every command you would run, be ready to say what result would rule something out. "I would run iostat" is weak; "if await is high and %util is near 100 the disk is the constraint" is the answer.
  3. 3Learn the two counter-intuitive ones properly: load average counts IO-blocked processes on Linux, and a deleted file still held open keeps its space. Both come up as gotchas.
  4. 4Know why getent hosts is a better diagnostic than dig. It uses the same resolution path the application does, and it is a detail that reads as real experience.
  5. 5Be honest when you do not know, then say how you would find out. On senior Linux questions, method is what is being scored.

Learn the underlying material

Other question sets