\\n\\t' — stops word-splitting on spaces, so filenames with spaces don't explode. Beyond the preamble: quote every variable ( \"$var\" ), use trap for cleanup, validate inputs, prefer [[ ]] over [ ] , and run shellcheck in CI."}},{"@type":"Question","name":"Bash essentials: variables, conditionals, loops, functions, and exit codes.","acceptedAnswer":{"@type":"Answer","text":"Exit codes are the foundation: 0 = success, non-zero = failure, available in $? . Every conditional in bash is really an exit-code test. Special variables : $0 script name, $1..$9 positional args, $# arg count, \"$@\" all args (quoted, preserves each), $ PID, $? last exit code. Test operators : strings = , != , -z (empty), -n (non-empty); numbers -eq -ne -gt -lt -ge -le ; files -f (file), -d (dir), -e (exists), -r/-w/-x , -s (non-empty file). Parameter expansion is the bash superpower worth knowing: ${VAR:-default} (use default if unset), ${VAR:?error msg} (fail if unset), ${VAR#prefix} / ${VAR%suffix} (strip), ${VAR/old/new} (replace)."}},{"@type":"Question","name":"Explain stdin/stdout/stderr, pipes, and redirection.","acceptedAnswer":{"@type":"Answer","text":"Every process starts with three file descriptors: 0 = stdin , 1 = stdout (normal output), 2 = stderr (errors, deliberately separate so you can filter them apart). > overwrite, >> append (stdout only). 2> redirect stderr, 2>&1 merge stderr into stdout, &>file both. < feed a file as stdin; <<EOF heredoc for inline multi-line input. | pipe stdout of one command into stdin of the next. /dev/null — the bit bucket. 2>/dev/null silences errors. tee — write to a file and pass through, so you can see and save. Order matters : cmd > file 2>&1 sends both to the file, but cmd 2>&1 > file sends stderr to the terminal (stderr was pointed at the old stdout before stdout was redirected). This is a classic interview trick."}},{"@type":"Question","name":"How do you use find and xargs for bulk operations safely?","acceptedAnswer":{"@type":"Answer","text":"find walks a tree filtering by name, type, size, age, permissions, or owner; xargs turns that list into arguments for another command. Common filters: -name / -iname , -type f|d|l , -size +100M , -mtime +7 (modified more than 7 days ago), -user , -perm , -maxdepth . The safety rules that matter in production: Use -print0 with xargs -0 (or just -exec ... + ) so filenames with spaces or newlines don't split into wrong arguments. Always dry-run first — run the find alone, or with -print , before attaching -delete or rm . Prefer -exec cmd {} + over \\; — it batches arguments instead of forking once per file (dramatically faster on thousands of files). Add -maxdepth and an absolute starting path to bound the blast radius."}},{"@type":"Question","name":"How does SSH key authentication work? What about config, agent forwarding, and tunnels?","acceptedAnswer":{"@type":"Answer","text":"Key auth : you generate a keypair; the public key goes into ~/.ssh/authorized_keys on the server; the private key never leaves your machine. At login the server sends a challenge that only the private key can sign — so no password crosses the wire and there's nothing on the server worth stealing. Practical pieces: Permissions matter — SSH refuses keys if they're too open: 700 on ~/.ssh , 600 on the private key and authorized_keys . This is the #1 \"key doesn't work\" cause. ~/.ssh/config — aliases, users, keys, and jump hosts so you type ssh prod-db instead of a 90-character command. Bastion/jump host — ProxyJump is the modern way to reach private instances. Agent forwarding ( -A ) lets a remote host use your local key — convenient but risky : root on that host can hijack your agent socket. Prefer ProxyJump . Tunnels — local forward ( -L ) to reach a private DB from your laptop; remote forward ( -R ); dynamic ( -D ) as a SOCKS proxy. Hardening the server: disable password auth and root login, use ed25519 keys, and fail2ban."}},{"@type":"Question","name":"Explain cron syntax. Why does a job that works manually fail under cron?","acceptedAnswer":{"@type":"Answer","text":"Format: minute hour day-of-month month day-of-week command . */5 * * * * every 5 minutes 0 2 * * * daily at 02:00 0 3 * * 0 Sundays at 03:00 30 1 1 * * 1st of each month, 01:30 Why it works manually but fails in cron — cron runs with a minimal environment : PATH is tiny (usually /usr/bin:/bin ) — so docker , aws , kubectl aren't found. Use absolute paths. No shell profile — .bashrc / .bash_profile are not sourced, so your exported env vars don't exist. No TTY , and the working directory is $HOME , not where you expect. Output goes to mail, not your terminal — if mail isn't configured, errors vanish silently. Always redirect to a log. % has special meaning in crontab and must be escaped as \\% . Modern alternative: systemd timers — better logging (journal), dependency ordering, Persistent=true to catch up on missed runs after downtime, and randomised delays."}},{"@type":"Question","name":"How do you read Linux memory usage? What is the OOM killer?","acceptedAnswer":{"@type":"Answer","text":"The big misconception: \"free\" memory being near zero is normal and good. Linux uses spare RAM for page cache and buffers, which are instantly reclaimable. The number that matters is available . free -h → look at available , not free . buff/cache — file cache; reclaimed automatically under pressure. Swap in use is not automatically bad ; what matters is active swapping ( si / so columns in vmstat ), which means thrashing. The OOM killer : when the kernel can't satisfy an allocation and can't reclaim, it picks a process to kill based on an oom_score (roughly: memory used, weighted by oom_score_adj ) and SIGKILLs it. Your app dies with no stack trace and no graceful shutdown — the only evidence is in dmesg /journal. In containers this is sharper: exceeding a container memory limit triggers a cgroup OOM kill → the container restarts with exit code 137 (128+9). Repeated 137s in Kubernetes = OOMKilled CrashLoopBackOff, and the fix is either raising the limit or fixing the leak — not adding swap."}},{"@type":"Question","name":"How do you manage users, groups, and sudo access on a server?","acceptedAnswer":{"@type":"Answer","text":"Users live in /etc/passwd (name, UID, GID, home, shell), password hashes in /etc/shadow (root-only), groups in /etc/group . useradd -m -s /bin/bash alice — create with home dir and shell. usermod -aG docker alice — add to a group. The -a is critical : without it you replace all secondary groups and can lock someone out. Service accounts should be no-login: useradd -r -s /usr/sbin/nologin appuser . Applications must never run as root. sudo is configured in /etc/sudoers — always edited with visudo , which validates syntax before saving (a broken sudoers file can lock everyone out of root). Prefer drop-in files in /etc/sudoers.d/ so config is manageable by Ansible/Terraform. Grant the narrowest command set that works, not blanket ALL . Every sudo invocation is logged — that audit trail is the whole point of sudo over shared root passwords."}},{"@type":"Question","name":"Hard link vs soft (symbolic) link — and what is an inode?","acceptedAnswer":{"@type":"Answer","text":"An inode is the actual file metadata structure — permissions, owner, timestamps, size, and pointers to the data blocks. Crucially, the filename is not in the inode ; a directory entry maps a name → inode number. That single fact explains both link types. Hard link — another directory entry pointing at the same inode . Both names are equally \"real\"; deleting one just decrements the link count, and the data survives until the count hits zero. Limits: cannot cross filesystems, cannot link directories. Soft/symbolic link — a tiny separate file whose content is a path . Can cross filesystems and link directories, but breaks if the target moves or is deleted (a \"dangling\" link). This is what /etc/alternatives and most deployment \"current release\" pointers use."}},{"@type":"Question","name":"Explain the Linux directory structure — which directories matter for DevOps?","acceptedAnswer":{"@type":"Answer","text":"/etc — system and application configuration . Everything you'd version-control or template with Ansible lives here. /var — variable data: /var/log (logs — the first place you look), /var/lib (application state: Docker images, databases), /var/spool . This is the partition that fills up. /opt — third-party/self-installed application software. Common deploy target. /usr — read-only system programs and libraries ( /usr/bin , /usr/local/bin for locally installed tools). /proc — virtual filesystem exposing kernel and per-process state ( /proc/PID/ , /proc/meminfo ). Not on disk. /sys — kernel/device and cgroup interface (container limits live here). /tmp — world-writable scratch, sticky bit set, cleared on reboot. /dev/shm — shared memory. /home , /root — user home directories. /dev — device files ( /dev/null , /dev/sda )."}},{"@type":"Question","name":"How do package managers work — apt vs yum/dnf? How do you pin a version?","acceptedAnswer":{"@type":"Answer","text":"Package managers resolve dependencies, verify signatures, and track what's installed so upgrades and removals are clean. Debian/Ubuntu — .deb packages; dpkg is the low-level tool, apt adds repository and dependency resolution. Repos configured in /etc/apt/sources.list(.d) . RHEL/CentOS/Amazon Linux — .rpm ; rpm is low-level, yum / dnf handles repos and dependencies. Repos in /etc/yum.repos.d/ . Version pinning matters for reproducibility. Installing nginx unpinned means two servers built a week apart run different versions — the exact class of bug that makes environments drift. Pin explicitly ( apt-mark hold , yum versionlock , or an exact version in the install command) and let a scheduled process handle upgrades deliberately. In containers this is the same rule as pinning base images: apt-get install -y nginx=1.24.0-1 , and clean the package cache in the same RUN layer to keep the image small."}},{"@type":"Question","name":"tar, gzip, rsync, scp — how do you archive and transfer files efficiently?","acceptedAnswer":{"@type":"Answer","text":"tar bundles many files into one archive (it doesn't compress by itself); -z adds gzip, -j bzip2, -J xz (best ratio, slowest). scp is a simple whole-file copy over SSH. rsync is what you actually want for anything repeated, because it: Transfers only the differences (delta algorithm) — a huge win on repeat syncs. Resumes interrupted transfers ( --partial ). Preserves permissions/ownership/timestamps ( -a ). Can --delete to mirror exactly, and --dry-run to preview first. Supports --exclude patterns and bandwidth limits. The trailing-slash rule is the classic rsync gotcha: rsync -a src/ dst/ copies the contents of src into dst, while rsync -a src dst/ creates dst/src/ . Getting this wrong with --delete is how people destroy directories."}},{"@type":"Question","name":"What Linux features actually make containers work — namespaces and cgroups?","acceptedAnswer":{"@type":"Answer","text":"A container is not a VM — it's an ordinary Linux process with restricted visibility and resources. Two kernel features do all the work: Namespaces = isolation (what a process can SEE): PID — its own process tree; the container's main process is PID 1. NET — own interfaces, IPs, routing table, ports. MNT — own filesystem view (the image's root). UTS — own hostname. IPC — own shared memory. USER — UID mapping (root inside ≠ root outside). cgroups = limits (what a process can USE): CPU shares/quota, memory limit (exceed it → OOM kill, exit 137), block I/O, PIDs count. Add union filesystems (OverlayFS) for layered images and copy-on-write, plus capabilities/seccomp/AppArmor for syscall restriction, and you have Docker. That's why containers start in milliseconds and share the host kernel — and also why a kernel exploit is a container-escape risk in a way it isn't for a VM."}}]}

interviewDeck

Your one-stop platform to prepare, practice and ace your interviews.

Loading your questions…

All Questions

Filters & tools
\n\t' — stops word-splitting on spaces, so filenames with spaces don't explode.

Beyond the preamble: quote every variable ("$var"), use trap for cleanup, validate inputs, prefer [[ ]] over [ ], and run shellcheck in CI.

#!/usr/bin/env bash
set -euo pipefail
IFS=

    
    
  

  





  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  

\\n\\t' — stops word-splitting on spaces, so filenames with spaces don't explode. Beyond the preamble: quote every variable ( \"$var\" ), use trap for cleanup, validate inputs, prefer [[ ]] over [ ] , and run shellcheck in CI."}},{"@type":"Question","name":"Bash essentials: variables, conditionals, loops, functions, and exit codes.","acceptedAnswer":{"@type":"Answer","text":"Exit codes are the foundation: 0 = success, non-zero = failure, available in $? . Every conditional in bash is really an exit-code test. Special variables : $0 script name, $1..$9 positional args, $# arg count, \"$@\" all args (quoted, preserves each), $ PID, $? last exit code. Test operators : strings = , != , -z (empty), -n (non-empty); numbers -eq -ne -gt -lt -ge -le ; files -f (file), -d (dir), -e (exists), -r/-w/-x , -s (non-empty file). Parameter expansion is the bash superpower worth knowing: ${VAR:-default} (use default if unset), ${VAR:?error msg} (fail if unset), ${VAR#prefix} / ${VAR%suffix} (strip), ${VAR/old/new} (replace)."}},{"@type":"Question","name":"Explain stdin/stdout/stderr, pipes, and redirection.","acceptedAnswer":{"@type":"Answer","text":"Every process starts with three file descriptors: 0 = stdin , 1 = stdout (normal output), 2 = stderr (errors, deliberately separate so you can filter them apart). > overwrite, >> append (stdout only). 2> redirect stderr, 2>&1 merge stderr into stdout, &>file both. < feed a file as stdin; <<EOF heredoc for inline multi-line input. | pipe stdout of one command into stdin of the next. /dev/null — the bit bucket. 2>/dev/null silences errors. tee — write to a file and pass through, so you can see and save. Order matters : cmd > file 2>&1 sends both to the file, but cmd 2>&1 > file sends stderr to the terminal (stderr was pointed at the old stdout before stdout was redirected). This is a classic interview trick."}},{"@type":"Question","name":"How do you use find and xargs for bulk operations safely?","acceptedAnswer":{"@type":"Answer","text":"find walks a tree filtering by name, type, size, age, permissions, or owner; xargs turns that list into arguments for another command. Common filters: -name / -iname , -type f|d|l , -size +100M , -mtime +7 (modified more than 7 days ago), -user , -perm , -maxdepth . The safety rules that matter in production: Use -print0 with xargs -0 (or just -exec ... + ) so filenames with spaces or newlines don't split into wrong arguments. Always dry-run first — run the find alone, or with -print , before attaching -delete or rm . Prefer -exec cmd {} + over \\; — it batches arguments instead of forking once per file (dramatically faster on thousands of files). Add -maxdepth and an absolute starting path to bound the blast radius."}},{"@type":"Question","name":"How does SSH key authentication work? What about config, agent forwarding, and tunnels?","acceptedAnswer":{"@type":"Answer","text":"Key auth : you generate a keypair; the public key goes into ~/.ssh/authorized_keys on the server; the private key never leaves your machine. At login the server sends a challenge that only the private key can sign — so no password crosses the wire and there's nothing on the server worth stealing. Practical pieces: Permissions matter — SSH refuses keys if they're too open: 700 on ~/.ssh , 600 on the private key and authorized_keys . This is the #1 \"key doesn't work\" cause. ~/.ssh/config — aliases, users, keys, and jump hosts so you type ssh prod-db instead of a 90-character command. Bastion/jump host — ProxyJump is the modern way to reach private instances. Agent forwarding ( -A ) lets a remote host use your local key — convenient but risky : root on that host can hijack your agent socket. Prefer ProxyJump . Tunnels — local forward ( -L ) to reach a private DB from your laptop; remote forward ( -R ); dynamic ( -D ) as a SOCKS proxy. Hardening the server: disable password auth and root login, use ed25519 keys, and fail2ban."}},{"@type":"Question","name":"Explain cron syntax. Why does a job that works manually fail under cron?","acceptedAnswer":{"@type":"Answer","text":"Format: minute hour day-of-month month day-of-week command . */5 * * * * every 5 minutes 0 2 * * * daily at 02:00 0 3 * * 0 Sundays at 03:00 30 1 1 * * 1st of each month, 01:30 Why it works manually but fails in cron — cron runs with a minimal environment : PATH is tiny (usually /usr/bin:/bin ) — so docker , aws , kubectl aren't found. Use absolute paths. No shell profile — .bashrc / .bash_profile are not sourced, so your exported env vars don't exist. No TTY , and the working directory is $HOME , not where you expect. Output goes to mail, not your terminal — if mail isn't configured, errors vanish silently. Always redirect to a log. % has special meaning in crontab and must be escaped as \\% . Modern alternative: systemd timers — better logging (journal), dependency ordering, Persistent=true to catch up on missed runs after downtime, and randomised delays."}},{"@type":"Question","name":"How do you read Linux memory usage? What is the OOM killer?","acceptedAnswer":{"@type":"Answer","text":"The big misconception: \"free\" memory being near zero is normal and good. Linux uses spare RAM for page cache and buffers, which are instantly reclaimable. The number that matters is available . free -h → look at available , not free . buff/cache — file cache; reclaimed automatically under pressure. Swap in use is not automatically bad ; what matters is active swapping ( si / so columns in vmstat ), which means thrashing. The OOM killer : when the kernel can't satisfy an allocation and can't reclaim, it picks a process to kill based on an oom_score (roughly: memory used, weighted by oom_score_adj ) and SIGKILLs it. Your app dies with no stack trace and no graceful shutdown — the only evidence is in dmesg /journal. In containers this is sharper: exceeding a container memory limit triggers a cgroup OOM kill → the container restarts with exit code 137 (128+9). Repeated 137s in Kubernetes = OOMKilled CrashLoopBackOff, and the fix is either raising the limit or fixing the leak — not adding swap."}},{"@type":"Question","name":"How do you manage users, groups, and sudo access on a server?","acceptedAnswer":{"@type":"Answer","text":"Users live in /etc/passwd (name, UID, GID, home, shell), password hashes in /etc/shadow (root-only), groups in /etc/group . useradd -m -s /bin/bash alice — create with home dir and shell. usermod -aG docker alice — add to a group. The -a is critical : without it you replace all secondary groups and can lock someone out. Service accounts should be no-login: useradd -r -s /usr/sbin/nologin appuser . Applications must never run as root. sudo is configured in /etc/sudoers — always edited with visudo , which validates syntax before saving (a broken sudoers file can lock everyone out of root). Prefer drop-in files in /etc/sudoers.d/ so config is manageable by Ansible/Terraform. Grant the narrowest command set that works, not blanket ALL . Every sudo invocation is logged — that audit trail is the whole point of sudo over shared root passwords."}},{"@type":"Question","name":"Hard link vs soft (symbolic) link — and what is an inode?","acceptedAnswer":{"@type":"Answer","text":"An inode is the actual file metadata structure — permissions, owner, timestamps, size, and pointers to the data blocks. Crucially, the filename is not in the inode ; a directory entry maps a name → inode number. That single fact explains both link types. Hard link — another directory entry pointing at the same inode . Both names are equally \"real\"; deleting one just decrements the link count, and the data survives until the count hits zero. Limits: cannot cross filesystems, cannot link directories. Soft/symbolic link — a tiny separate file whose content is a path . Can cross filesystems and link directories, but breaks if the target moves or is deleted (a \"dangling\" link). This is what /etc/alternatives and most deployment \"current release\" pointers use."}},{"@type":"Question","name":"Explain the Linux directory structure — which directories matter for DevOps?","acceptedAnswer":{"@type":"Answer","text":"/etc — system and application configuration . Everything you'd version-control or template with Ansible lives here. /var — variable data: /var/log (logs — the first place you look), /var/lib (application state: Docker images, databases), /var/spool . This is the partition that fills up. /opt — third-party/self-installed application software. Common deploy target. /usr — read-only system programs and libraries ( /usr/bin , /usr/local/bin for locally installed tools). /proc — virtual filesystem exposing kernel and per-process state ( /proc/PID/ , /proc/meminfo ). Not on disk. /sys — kernel/device and cgroup interface (container limits live here). /tmp — world-writable scratch, sticky bit set, cleared on reboot. /dev/shm — shared memory. /home , /root — user home directories. /dev — device files ( /dev/null , /dev/sda )."}},{"@type":"Question","name":"How do package managers work — apt vs yum/dnf? How do you pin a version?","acceptedAnswer":{"@type":"Answer","text":"Package managers resolve dependencies, verify signatures, and track what's installed so upgrades and removals are clean. Debian/Ubuntu — .deb packages; dpkg is the low-level tool, apt adds repository and dependency resolution. Repos configured in /etc/apt/sources.list(.d) . RHEL/CentOS/Amazon Linux — .rpm ; rpm is low-level, yum / dnf handles repos and dependencies. Repos in /etc/yum.repos.d/ . Version pinning matters for reproducibility. Installing nginx unpinned means two servers built a week apart run different versions — the exact class of bug that makes environments drift. Pin explicitly ( apt-mark hold , yum versionlock , or an exact version in the install command) and let a scheduled process handle upgrades deliberately. In containers this is the same rule as pinning base images: apt-get install -y nginx=1.24.0-1 , and clean the package cache in the same RUN layer to keep the image small."}},{"@type":"Question","name":"tar, gzip, rsync, scp — how do you archive and transfer files efficiently?","acceptedAnswer":{"@type":"Answer","text":"tar bundles many files into one archive (it doesn't compress by itself); -z adds gzip, -j bzip2, -J xz (best ratio, slowest). scp is a simple whole-file copy over SSH. rsync is what you actually want for anything repeated, because it: Transfers only the differences (delta algorithm) — a huge win on repeat syncs. Resumes interrupted transfers ( --partial ). Preserves permissions/ownership/timestamps ( -a ). Can --delete to mirror exactly, and --dry-run to preview first. Supports --exclude patterns and bandwidth limits. The trailing-slash rule is the classic rsync gotcha: rsync -a src/ dst/ copies the contents of src into dst, while rsync -a src dst/ creates dst/src/ . Getting this wrong with --delete is how people destroy directories."}},{"@type":"Question","name":"What Linux features actually make containers work — namespaces and cgroups?","acceptedAnswer":{"@type":"Answer","text":"A container is not a VM — it's an ordinary Linux process with restricted visibility and resources. Two kernel features do all the work: Namespaces = isolation (what a process can SEE): PID — its own process tree; the container's main process is PID 1. NET — own interfaces, IPs, routing table, ports. MNT — own filesystem view (the image's root). UTS — own hostname. IPC — own shared memory. USER — UID mapping (root inside ≠ root outside). cgroups = limits (what a process can USE): CPU shares/quota, memory limit (exceed it → OOM kill, exit 137), block I/O, PIDs count. Add union filesystems (OverlayFS) for layered images and copy-on-write, plus capabilities/seccomp/AppArmor for syscall restriction, and you have Docker. That's why containers start in milliseconds and share the host kernel — and also why a kernel exploit is a container-escape risk in a way it isn't for a VM."}}]}
  



  
  

interviewDeck

Your one-stop platform to prepare, practice and ace your interviews.

Loading your questions…

All Questions

Filters & tools
\n\t' readonly LOG_DIR="${LOG_DIR:-/var/log/myapp}" # default if unset readonly TMP=$(mktemp -d) trap 'rm -rf "$TMP"' EXIT # cleanup on ANY exit usage() { echo "usage: $0 <env>" >&2; exit 1; } [[ $# -eq 1 ]] || usage env="$1" if [[ ! "$env" =~ ^(dev|stage|prod)$ ]]; then echo "invalid env: $env" >&2; exit 1 fi log() { printf '%s [%s] %s\n' "$(date -Is)" "$1" "${*:2}" >&2; } deploy() { local target="$1" log INFO "deploying to $target" curl -fsS --retry 3 --max-time 30 "https://$target/health" || { log ERROR "health check failed"; return 1 } } deploy "$env" log INFO done

Bash essentials: variables, conditionals, loops, functions, and exit codes.

Exit codes are the foundation: 0 = success, non-zero = failure, available in $?. Every conditional in bash is really an exit-code test.

Special variables: $0 script name, $1..$9 positional args, $# arg count, "$@" all args (quoted, preserves each), $ PID, $? last exit code.

Test operators: strings =, !=, -z (empty), -n (non-empty); numbers -eq -ne -gt -lt -ge -le; files -f (file), -d (dir), -e (exists), -r/-w/-x, -s (non-empty file).

Parameter expansion is the bash superpower worth knowing: ${VAR:-default} (use default if unset), ${VAR:?error msg} (fail if unset), ${VAR#prefix}/${VAR%suffix} (strip), ${VAR/old/new} (replace).

#!/usr/bin/env bash
set -euo pipefail

name="${1:?usage: $0 <name>}"        # fail with a message if missing
retries="${RETRIES:-3}"              # default when unset

# conditional
if [[ -f /etc/myapp.conf && "$retries" -gt 0 ]]; then
  echo "config found"
elif [[ -d /etc/myapp ]]; then
  echo "dir only"
else
  echo "missing" >&2; exit 1
fi

# loops
for f in /var/log/*.log; do
  echo "rotating $f"
done

for i in {1..5}; do echo "attempt $i"; done

while read -r line; do
  [[ -z "$line" || "$line" == \#* ]] && continue
  echo "host: $line"
done < hosts.txt

# function with return code + local vars
retry() {
  local n=0 max="$1"; shift
  until "$@"; do
    n=$((n+1))
    (( n >= max )) && return 1
    sleep $(( 2 ** n ))            # exponential backoff
  done
}
retry 3 curl -fsS https://api.example.com/health

case "$name" in
  dev|stage) echo "non-prod" ;;
  prod)      echo "PROD — confirm" ;;
  *)         echo "unknown" >&2; exit 1 ;;
esac

Explain stdin/stdout/stderr, pipes, and redirection.

Every process starts with three file descriptors: 0 = stdin, 1 = stdout (normal output), 2 = stderr (errors, deliberately separate so you can filter them apart).

Order matters: cmd > file 2>&1 sends both to the file, but cmd 2>&1 > file sends stderr to the terminal (stderr was pointed at the old stdout before stdout was redirected). This is a classic interview trick.

cmd > out.log 2> err.log        # split streams
cmd > all.log 2>&1              # BOTH to file (correct order)
cmd &> all.log                  # same, bash shorthand
cmd 2>/dev/null                 # discard errors only
cmd | tee -a run.log            # see AND save

# only stderr through grep
cmd 2>&1 >/dev/null | grep -i warn

# heredoc
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata: { name: demo }
EOF

# process substitution — compare two command outputs
diff <(ssh host1 'rpm -qa | sort') <(ssh host2 'rpm -qa | sort')

How do you use find and xargs for bulk operations safely?

find walks a tree filtering by name, type, size, age, permissions, or owner; xargs turns that list into arguments for another command.

Common filters: -name/-iname, -type f|d|l, -size +100M, -mtime +7 (modified more than 7 days ago), -user, -perm, -maxdepth.

The safety rules that matter in production:

# ALWAYS look first
find /var/log -type f -name '*.log' -mtime +30

# then act
find /var/log -type f -name '*.log' -mtime +30 -delete

# safe with weird filenames, batched exec
find /data -type f -name '*.tmp' -print0 | xargs -0 -r rm -v
find /data -type f -name '*.tmp' -exec rm -v {} +

# biggest files
find / -xdev -type f -size +500M -exec ls -lh {} + 2>/dev/null | sort -k5 -h

# parallel processing
find . -name '*.gz' -print0 | xargs -0 -P 4 -n 1 gunzip

# empty dirs, broken symlinks, wrong ownership
find /opt -type d -empty -delete
find /opt -xtype l
find /opt/app ! -user appuser -exec chown appuser:appgroup {} +

How does SSH key authentication work? What about config, agent forwarding, and tunnels?

Key auth: you generate a keypair; the public key goes into ~/.ssh/authorized_keys on the server; the private key never leaves your machine. At login the server sends a challenge that only the private key can sign — so no password crosses the wire and there's nothing on the server worth stealing.

Practical pieces:

Hardening the server: disable password auth and root login, use ed25519 keys, and fail2ban.

ssh-keygen -t ed25519 -C "chinmaya@laptop"
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server

# ~/.ssh/config
Host bastion
  HostName 203.0.113.10
  User ec2-user
  IdentityFile ~/.ssh/prod.pem

Host prod-*
  User appuser
  ProxyJump bastion            # safer than agent forwarding
  IdentityFile ~/.ssh/prod.pem
  ServerAliveInterval 60

# local port forward: reach a private RDS from localhost:5432
ssh -N -L 5432:mydb.internal:5432 bastion

# permissions (the classic failure)
chmod 700 ~/.ssh && chmod 600 ~/.ssh/id_ed25519 ~/.ssh/authorized_keys

ssh -vvv user@host   # debug auth failures

Explain cron syntax. Why does a job that works manually fail under cron?

Format: minute hour day-of-month month day-of-week command.

*/5 * * * *      every 5 minutes
0 2 * * *        daily at 02:00
0 3 * * 0        Sundays at 03:00
30 1 1 * *       1st of each month, 01:30

Why it works manually but fails in cron — cron runs with a minimal environment:

Modern alternative: systemd timers — better logging (journal), dependency ordering, Persistent=true to catch up on missed runs after downtime, and randomised delays.

crontab -e ; crontab -l ; crontab -l -u appuser

# robust entry: absolute paths, explicit env, logged output, no overlap
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
0 2 * * * /usr/bin/flock -n /tmp/backup.lock /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

# systemd timer equivalent
# /etc/systemd/system/backup.timer
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true          # run on boot if the machine was off
RandomizedDelaySec=300

How do you read Linux memory usage? What is the OOM killer?

The big misconception: "free" memory being near zero is normal and good. Linux uses spare RAM for page cache and buffers, which are instantly reclaimable. The number that matters is available.

The OOM killer: when the kernel can't satisfy an allocation and can't reclaim, it picks a process to kill based on an oom_score (roughly: memory used, weighted by oom_score_adj) and SIGKILLs it. Your app dies with no stack trace and no graceful shutdown — the only evidence is in dmesg/journal.

In containers this is sharper: exceeding a container memory limit triggers a cgroup OOM kill → the container restarts with exit code 137 (128+9). Repeated 137s in Kubernetes = OOMKilled CrashLoopBackOff, and the fix is either raising the limit or fixing the leak — not adding swap.

free -h                       # read the 'available' column
vmstat 1 5                    # si/so non-zero = actively swapping

# who is using memory
ps -eo pid,rss,%mem,cmd --sort=-rss | head
smem -rs pss 2>/dev/null | head

# was something OOM-killed?
dmesg -T | grep -i -E 'killed process|out of memory'
journalctl -k | grep -i oom

# container/cgroup limits
cat /sys/fs/cgroup/memory.max /sys/fs/cgroup/memory.current
kubectl describe pod mypod | grep -A3 'Last State'   # Reason: OOMKilled, Exit Code: 137

# protect a critical process (lower = less likely to be killed)
choom -n -500 -p $(pidof critical-app)

How do you manage users, groups, and sudo access on a server?

Users live in /etc/passwd (name, UID, GID, home, shell), password hashes in /etc/shadow (root-only), groups in /etc/group.

sudo is configured in /etc/sudoersalways edited with visudo, which validates syntax before saving (a broken sudoers file can lock everyone out of root). Prefer drop-in files in /etc/sudoers.d/ so config is manageable by Ansible/Terraform.

Grant the narrowest command set that works, not blanket ALL. Every sudo invocation is logged — that audit trail is the whole point of sudo over shared root passwords.

useradd -m -s /bin/bash -G developers alice
passwd alice
usermod -aG docker alice          # -a = APPEND (omitting it wipes other groups)
id alice ; groups alice

# service account: no login, no shell
useradd -r -s /usr/sbin/nologin -d /opt/myapp appuser

# least-privilege sudo drop-in
# /etc/sudoers.d/deploy   (validate: visudo -cf)
%deployers ALL=(root) NOPASSWD: /bin/systemctl restart myapp, /bin/systemctl status myapp

sudo -l -U alice                  # what can alice actually run?
lastlog ; last -20                # login history
grep 'sudo:' /var/log/auth.log    # sudo audit trail

Hard link vs soft (symbolic) link — and what is an inode?

An inode is the actual file metadata structure — permissions, owner, timestamps, size, and pointers to the data blocks. Crucially, the filename is not in the inode; a directory entry maps a name → inode number. That single fact explains both link types.

ln  file.txt hard.txt        # hard link — same inode
ln -s file.txt soft.txt      # symlink — stores a path

ls -li                       # -i shows inode numbers
# 1234567 -rw-r--r-- 2 user user   12 file.txt   ← link count 2
# 1234567 -rw-r--r-- 2 user user   12 hard.txt   ← same inode
# 1234890 lrwxrwxrwx 1 user user    8 soft.txt -> file.txt

stat file.txt                # inode, links, timestamps
find /path -inum 1234567     # all names for one inode

# atomic release switch (classic deploy pattern)
ln -sfn /opt/app/releases/2026-08-05 /opt/app/current
systemctl restart myapp

Explain the Linux directory structure — which directories matter for DevOps?

du -xh --max-depth=1 /var | sort -h    # /var/log and /var/lib grow
cat /proc/meminfo /proc/cpuinfo
cat /proc/$(pidof nginx)/limits        # actual ulimits of a running process
ls -l /proc/PID/cwd /proc/PID/exe      # where it runs from
cat /sys/fs/cgroup/memory.max          # container memory limit
df -hT                                 # mounts + filesystem types

How do package managers work — apt vs yum/dnf? How do you pin a version?

Package managers resolve dependencies, verify signatures, and track what's installed so upgrades and removals are clean.

Version pinning matters for reproducibility. Installing nginx unpinned means two servers built a week apart run different versions — the exact class of bug that makes environments drift. Pin explicitly (apt-mark hold, yum versionlock, or an exact version in the install command) and let a scheduled process handle upgrades deliberately.

In containers this is the same rule as pinning base images: apt-get install -y nginx=1.24.0-1, and clean the package cache in the same RUN layer to keep the image small.

# Debian/Ubuntu
apt-get update && apt-get install -y nginx=1.24.0-1ubuntu1
apt-cache policy nginx          # available versions + which repo
apt-mark hold nginx             # pin
dpkg -l | grep nginx ; dpkg -L nginx ; dpkg -S /usr/sbin/nginx

# RHEL family
dnf install -y nginx-1.24.0
dnf list available nginx --showduplicates
dnf versionlock add nginx
rpm -qa | grep nginx ; rpm -ql nginx ; rpm -qf /usr/sbin/nginx

# in a Dockerfile — pin AND clean in the same layer
RUN apt-get update \
 && apt-get install -y --no-install-recommends curl=7.88.1-10 \
 && rm -rf /var/lib/apt/lists/*

tar, gzip, rsync, scp — how do you archive and transfer files efficiently?

tar bundles many files into one archive (it doesn't compress by itself); -z adds gzip, -j bzip2, -J xz (best ratio, slowest).

scp is a simple whole-file copy over SSH. rsync is what you actually want for anything repeated, because it:

The trailing-slash rule is the classic rsync gotcha: rsync -a src/ dst/ copies the contents of src into dst, while rsync -a src dst/ creates dst/src/. Getting this wrong with --delete is how people destroy directories.

tar -czvf backup.tar.gz /opt/app        # create gzip
tar -xzvf backup.tar.gz -C /restore     # extract to a directory
tar -tzvf backup.tar.gz | head          # LIST before extracting

# rsync — preview first, then run
rsync -avz --delete --dry-run /opt/app/ user@host:/opt/app/
rsync -avzP --exclude='*.log' --exclude='node_modules' \
      /opt/app/ user@host:/opt/app/

# through a bastion, with bandwidth cap
rsync -avz -e 'ssh -J bastion' --bwlimit=5000 /data/ user@prod:/data/

# stream a directory over SSH without a temp file
tar -czf - /opt/app | ssh user@host 'tar -xzf - -C /restore'

What Linux features actually make containers work — namespaces and cgroups?

A container is not a VM — it's an ordinary Linux process with restricted visibility and resources. Two kernel features do all the work:

Namespaces = isolation (what a process can SEE):

cgroups = limits (what a process can USE): CPU shares/quota, memory limit (exceed it → OOM kill, exit 137), block I/O, PIDs count.

Add union filesystems (OverlayFS) for layered images and copy-on-write, plus capabilities/seccomp/AppArmor for syscall restriction, and you have Docker. That's why containers start in milliseconds and share the host kernel — and also why a kernel exploit is a container-escape risk in a way it isn't for a VM.

# a container is just a process on the host
ps -ef | grep myapp
sudo ls -l /proc/<pid>/ns/          # its namespaces
sudo nsenter -t <pid> -n ss -ltnp   # run in the container's NET namespace

# cgroup limits (v2)
cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/cpu.max

# create isolation by hand — this IS a mini container
sudo unshare --pid --net --mount --uts --fork --mount-proc bash

docker run --memory=512m --cpus=1.5 --pids-limit=100 myapp
\\n\\t' — stops word-splitting on spaces, so filenames with spaces don't explode. Beyond the preamble: quote every variable ( \"$var\" ), use trap for cleanup, validate inputs, prefer [[ ]] over [ ] , and run shellcheck in CI."}},{"@type":"Question","name":"Bash essentials: variables, conditionals, loops, functions, and exit codes.","acceptedAnswer":{"@type":"Answer","text":"Exit codes are the foundation: 0 = success, non-zero = failure, available in $? . Every conditional in bash is really an exit-code test. Special variables : $0 script name, $1..$9 positional args, $# arg count, \"$@\" all args (quoted, preserves each), $ PID, $? last exit code. Test operators : strings = , != , -z (empty), -n (non-empty); numbers -eq -ne -gt -lt -ge -le ; files -f (file), -d (dir), -e (exists), -r/-w/-x , -s (non-empty file). Parameter expansion is the bash superpower worth knowing: ${VAR:-default} (use default if unset), ${VAR:?error msg} (fail if unset), ${VAR#prefix} / ${VAR%suffix} (strip), ${VAR/old/new} (replace)."}},{"@type":"Question","name":"Explain stdin/stdout/stderr, pipes, and redirection.","acceptedAnswer":{"@type":"Answer","text":"Every process starts with three file descriptors: 0 = stdin , 1 = stdout (normal output), 2 = stderr (errors, deliberately separate so you can filter them apart). > overwrite, >> append (stdout only). 2> redirect stderr, 2>&1 merge stderr into stdout, &>file both. < feed a file as stdin; <<EOF heredoc for inline multi-line input. | pipe stdout of one command into stdin of the next. /dev/null — the bit bucket. 2>/dev/null silences errors. tee — write to a file and pass through, so you can see and save. Order matters : cmd > file 2>&1 sends both to the file, but cmd 2>&1 > file sends stderr to the terminal (stderr was pointed at the old stdout before stdout was redirected). This is a classic interview trick."}},{"@type":"Question","name":"How do you use find and xargs for bulk operations safely?","acceptedAnswer":{"@type":"Answer","text":"find walks a tree filtering by name, type, size, age, permissions, or owner; xargs turns that list into arguments for another command. Common filters: -name / -iname , -type f|d|l , -size +100M , -mtime +7 (modified more than 7 days ago), -user , -perm , -maxdepth . The safety rules that matter in production: Use -print0 with xargs -0 (or just -exec ... + ) so filenames with spaces or newlines don't split into wrong arguments. Always dry-run first — run the find alone, or with -print , before attaching -delete or rm . Prefer -exec cmd {} + over \\; — it batches arguments instead of forking once per file (dramatically faster on thousands of files). Add -maxdepth and an absolute starting path to bound the blast radius."}},{"@type":"Question","name":"How does SSH key authentication work? What about config, agent forwarding, and tunnels?","acceptedAnswer":{"@type":"Answer","text":"Key auth : you generate a keypair; the public key goes into ~/.ssh/authorized_keys on the server; the private key never leaves your machine. At login the server sends a challenge that only the private key can sign — so no password crosses the wire and there's nothing on the server worth stealing. Practical pieces: Permissions matter — SSH refuses keys if they're too open: 700 on ~/.ssh , 600 on the private key and authorized_keys . This is the #1 \"key doesn't work\" cause. ~/.ssh/config — aliases, users, keys, and jump hosts so you type ssh prod-db instead of a 90-character command. Bastion/jump host — ProxyJump is the modern way to reach private instances. Agent forwarding ( -A ) lets a remote host use your local key — convenient but risky : root on that host can hijack your agent socket. Prefer ProxyJump . Tunnels — local forward ( -L ) to reach a private DB from your laptop; remote forward ( -R ); dynamic ( -D ) as a SOCKS proxy. Hardening the server: disable password auth and root login, use ed25519 keys, and fail2ban."}},{"@type":"Question","name":"Explain cron syntax. Why does a job that works manually fail under cron?","acceptedAnswer":{"@type":"Answer","text":"Format: minute hour day-of-month month day-of-week command . */5 * * * * every 5 minutes 0 2 * * * daily at 02:00 0 3 * * 0 Sundays at 03:00 30 1 1 * * 1st of each month, 01:30 Why it works manually but fails in cron — cron runs with a minimal environment : PATH is tiny (usually /usr/bin:/bin ) — so docker , aws , kubectl aren't found. Use absolute paths. No shell profile — .bashrc / .bash_profile are not sourced, so your exported env vars don't exist. No TTY , and the working directory is $HOME , not where you expect. Output goes to mail, not your terminal — if mail isn't configured, errors vanish silently. Always redirect to a log. % has special meaning in crontab and must be escaped as \\% . Modern alternative: systemd timers — better logging (journal), dependency ordering, Persistent=true to catch up on missed runs after downtime, and randomised delays."}},{"@type":"Question","name":"How do you read Linux memory usage? What is the OOM killer?","acceptedAnswer":{"@type":"Answer","text":"The big misconception: \"free\" memory being near zero is normal and good. Linux uses spare RAM for page cache and buffers, which are instantly reclaimable. The number that matters is available . free -h → look at available , not free . buff/cache — file cache; reclaimed automatically under pressure. Swap in use is not automatically bad ; what matters is active swapping ( si / so columns in vmstat ), which means thrashing. The OOM killer : when the kernel can't satisfy an allocation and can't reclaim, it picks a process to kill based on an oom_score (roughly: memory used, weighted by oom_score_adj ) and SIGKILLs it. Your app dies with no stack trace and no graceful shutdown — the only evidence is in dmesg /journal. In containers this is sharper: exceeding a container memory limit triggers a cgroup OOM kill → the container restarts with exit code 137 (128+9). Repeated 137s in Kubernetes = OOMKilled CrashLoopBackOff, and the fix is either raising the limit or fixing the leak — not adding swap."}},{"@type":"Question","name":"How do you manage users, groups, and sudo access on a server?","acceptedAnswer":{"@type":"Answer","text":"Users live in /etc/passwd (name, UID, GID, home, shell), password hashes in /etc/shadow (root-only), groups in /etc/group . useradd -m -s /bin/bash alice — create with home dir and shell. usermod -aG docker alice — add to a group. The -a is critical : without it you replace all secondary groups and can lock someone out. Service accounts should be no-login: useradd -r -s /usr/sbin/nologin appuser . Applications must never run as root. sudo is configured in /etc/sudoers — always edited with visudo , which validates syntax before saving (a broken sudoers file can lock everyone out of root). Prefer drop-in files in /etc/sudoers.d/ so config is manageable by Ansible/Terraform. Grant the narrowest command set that works, not blanket ALL . Every sudo invocation is logged — that audit trail is the whole point of sudo over shared root passwords."}},{"@type":"Question","name":"Hard link vs soft (symbolic) link — and what is an inode?","acceptedAnswer":{"@type":"Answer","text":"An inode is the actual file metadata structure — permissions, owner, timestamps, size, and pointers to the data blocks. Crucially, the filename is not in the inode ; a directory entry maps a name → inode number. That single fact explains both link types. Hard link — another directory entry pointing at the same inode . Both names are equally \"real\"; deleting one just decrements the link count, and the data survives until the count hits zero. Limits: cannot cross filesystems, cannot link directories. Soft/symbolic link — a tiny separate file whose content is a path . Can cross filesystems and link directories, but breaks if the target moves or is deleted (a \"dangling\" link). This is what /etc/alternatives and most deployment \"current release\" pointers use."}},{"@type":"Question","name":"Explain the Linux directory structure — which directories matter for DevOps?","acceptedAnswer":{"@type":"Answer","text":"/etc — system and application configuration . Everything you'd version-control or template with Ansible lives here. /var — variable data: /var/log (logs — the first place you look), /var/lib (application state: Docker images, databases), /var/spool . This is the partition that fills up. /opt — third-party/self-installed application software. Common deploy target. /usr — read-only system programs and libraries ( /usr/bin , /usr/local/bin for locally installed tools). /proc — virtual filesystem exposing kernel and per-process state ( /proc/PID/ , /proc/meminfo ). Not on disk. /sys — kernel/device and cgroup interface (container limits live here). /tmp — world-writable scratch, sticky bit set, cleared on reboot. /dev/shm — shared memory. /home , /root — user home directories. /dev — device files ( /dev/null , /dev/sda )."}},{"@type":"Question","name":"How do package managers work — apt vs yum/dnf? How do you pin a version?","acceptedAnswer":{"@type":"Answer","text":"Package managers resolve dependencies, verify signatures, and track what's installed so upgrades and removals are clean. Debian/Ubuntu — .deb packages; dpkg is the low-level tool, apt adds repository and dependency resolution. Repos configured in /etc/apt/sources.list(.d) . RHEL/CentOS/Amazon Linux — .rpm ; rpm is low-level, yum / dnf handles repos and dependencies. Repos in /etc/yum.repos.d/ . Version pinning matters for reproducibility. Installing nginx unpinned means two servers built a week apart run different versions — the exact class of bug that makes environments drift. Pin explicitly ( apt-mark hold , yum versionlock , or an exact version in the install command) and let a scheduled process handle upgrades deliberately. In containers this is the same rule as pinning base images: apt-get install -y nginx=1.24.0-1 , and clean the package cache in the same RUN layer to keep the image small."}},{"@type":"Question","name":"tar, gzip, rsync, scp — how do you archive and transfer files efficiently?","acceptedAnswer":{"@type":"Answer","text":"tar bundles many files into one archive (it doesn't compress by itself); -z adds gzip, -j bzip2, -J xz (best ratio, slowest). scp is a simple whole-file copy over SSH. rsync is what you actually want for anything repeated, because it: Transfers only the differences (delta algorithm) — a huge win on repeat syncs. Resumes interrupted transfers ( --partial ). Preserves permissions/ownership/timestamps ( -a ). Can --delete to mirror exactly, and --dry-run to preview first. Supports --exclude patterns and bandwidth limits. The trailing-slash rule is the classic rsync gotcha: rsync -a src/ dst/ copies the contents of src into dst, while rsync -a src dst/ creates dst/src/ . Getting this wrong with --delete is how people destroy directories."}},{"@type":"Question","name":"What Linux features actually make containers work — namespaces and cgroups?","acceptedAnswer":{"@type":"Answer","text":"A container is not a VM — it's an ordinary Linux process with restricted visibility and resources. Two kernel features do all the work: Namespaces = isolation (what a process can SEE): PID — its own process tree; the container's main process is PID 1. NET — own interfaces, IPs, routing table, ports. MNT — own filesystem view (the image's root). UTS — own hostname. IPC — own shared memory. USER — UID mapping (root inside ≠ root outside). cgroups = limits (what a process can USE): CPU shares/quota, memory limit (exceed it → OOM kill, exit 137), block I/O, PIDs count. Add union filesystems (OverlayFS) for layered images and copy-on-write, plus capabilities/seccomp/AppArmor for syscall restriction, and you have Docker. That's why containers start in milliseconds and share the host kernel — and also why a kernel exploit is a container-escape risk in a way it isn't for a VM."}}]}

interviewDeck

Your one-stop platform to prepare, practice and ace your interviews.

Loading your questions…

All Questions

Filters & tools