Linux powers the vast majority of servers, containers, and cloud infrastructure. Whether you SSH into a production box, build a Docker image, or debug a failing service, you are speaking to a Linux kernel through a shell. This guide covers the core skills every engineer needs: navigating the filesystem, controlling permissions, managing processes and services, installing software, working with the network, and automating tasks with bash.
The Filesystem Hierarchy
Linux organizes everything under a single root directory /. There are no drive letters. The Filesystem Hierarchy Standard (FHS) defines what lives where, so you can find configuration and binaries on any distribution.
| Path | Purpose |
|---|---|
/etc | System-wide configuration files |
/home | Per-user home directories |
/var | Variable data: logs, spools, caches |
/usr | User-space programs and libraries |
/tmp | Temporary files, cleared on reboot |
/proc, /sys | Virtual filesystems exposing kernel and process state |
/opt | Optional third-party software |
pwd # print working directory
ls -lah # long listing, all files, human-readable sizes
cd /var/log # change directory
cd - # jump back to previous directory
tree -L 2 # show two levels of the tree
stat /etc/hostname # detailed metadata about a file
File Permissions and Ownership
Every file has an owner, a group, and three permission sets: user, group, and others. Each set grants read (r=4), write (w=2), and execute (x=1). Add the numbers to build an octal mode.
-rwxr-xr-- 1 alice devs 2048 Jun 01 10:00 deploy.sh
|└┬┘└┬┘└┬┘
| │ │ └── others: r-- (read)
| │ └───── group: r-x (read, execute)
| └──────── user: rwx (read, write, execute)
└────────── type: - file, d directory, l symlink
chmod 755 deploy.sh # rwx r-x r-x -> owner full, others read+exec
chmod 644 config.yaml # rw- r-- r-- -> common for config files
chmod +x script.sh # add execute bit for everyone (symbolic)
chmod u+w,g-w file # add write for user, remove for group
chown alice:devs file # change owner and group
chown -R alice /srv/app # recursive ownership change
umask 022 # default new files as 644, dirs as 755
Octal shortcut
Read=4, Write=2, Execute=1. Sum per role: 7=rwx, 6=rw-, 5=r-x, 4=r--. So chmod 640 means owner rw, group r, others none.
Users and Groups
User accounts live in /etc/passwd and groups in /etc/group. Use sudo to run a single command as another user (usually root) rather than logging in as root.
whoami # current username
id # uid, gid, and group memberships
sudo useradd -m -s /bin/bash deploy # create user with home + shell
sudo passwd deploy # set a password
sudo usermod -aG docker deploy # append user to the docker group
sudo userdel -r olduser # delete user and their home
groups deploy # list a user's groups
su - deploy # switch to another user's login shell
Processes and Services
A process is a running program with a numeric PID. Inspect them with ps and top, and stop them with kill. Long-running background services are managed by systemd through systemctl.
ps aux # every process, with CPU and memory
ps -ef | grep nginx # find a specific process
top # live process monitor (press q to quit)
htop # friendlier interactive monitor
kill 1234 # send SIGTERM (graceful) to PID 1234
kill -9 1234 # send SIGKILL (force)
pkill -f "python worker" # kill by matching command line
jobs; bg; fg # manage shell background jobs
sudo systemctl status nginx # is the service running?
sudo systemctl start nginx # start now
sudo systemctl enable --now nginx # start now and on boot
sudo systemctl restart nginx # restart
sudo systemctl reload nginx # reload config without downtime
journalctl -u nginx -f # follow a service's logs live
journalctl -p err -b # error-level logs since last boot
Package Managers
Debian and Ubuntu use apt; Fedora, RHEL, and Rocky use dnf. Both resolve dependencies for you.
| Task | apt (Debian/Ubuntu) | dnf (Fedora/RHEL) |
|---|---|---|
| Refresh index | sudo apt update | sudo dnf check-update |
| Install | sudo apt install nginx | sudo dnf install nginx |
| Upgrade all | sudo apt upgrade | sudo dnf upgrade |
| Remove | sudo apt remove nginx | sudo dnf remove nginx |
| Search | apt search curl | dnf search curl |
Networking
The modern toolkit is ip (replaces ifconfig) and ss (replaces netstat). Use curl to talk to HTTP services.
ip addr # show interfaces and IP addresses
ip route # routing table (find your default gateway)
ss -tulpn # listening TCP/UDP sockets with process names
ping -c 4 1.1.1.1 # test connectivity
dig example.com +short # DNS lookup
curl -sSL https://api.example.com/health # fetch a URL (silent, follow redirects)
curl -X POST -d '{"k":"v"}' -H 'Content-Type: application/json' URL
nc -zv host 5432 # check if a TCP port is open
Text Processing
Filtering and reshaping text is a daily task: parsing logs, extracting fields, and editing config files. grep searches, sed edits streams, awk works with columns, and find locates files.
grep -rn "ERROR" /var/log/app/ # recursive, with line numbers
grep -i -A3 "timeout" app.log # case-insensitive, 3 lines after match
sed 's/localhost/prod-db/g' cfg.conf # substitute all occurrences
sed -i '/^#/d' config.ini # delete comment lines in place
awk '{print $1, $9}' access.log # print columns 1 and 9
awk -F: '$3 >= 1000 {print $1}' /etc/passwd # regular user accounts
find /var/log -name "*.log" -mtime +7 -delete # remove logs older than 7 days
find . -type f -size +100M # large files
Pipes and Redirection
The Unix philosophy is small tools composed with pipes. | sends one command's stdout to the next command's stdin. Redirection sends output to files or discards it.
cat access.log | grep 500 | wc -l # count HTTP 500 responses
command > out.txt # stdout to file (overwrite)
command >> out.txt # stdout to file (append)
command 2> err.txt # stderr to file
command > all.txt 2>&1 # merge stderr into stdout
command > /dev/null 2>&1 # discard all output
sort access.log | uniq -c | sort -rn # top repeated lines
ps aux | sort -k3 -rn | head # top CPU consumers
Environment Variables
Environment variables configure programs and the shell. PATH lists directories searched for commands; export makes a variable available to child processes.
echo $PATH # inspect the search path
export API_KEY="secret123" # set for this shell and children
printenv | sort # list all environment variables
DB_HOST=localhost node app.js # set a var for one command only
# Persist across sessions by adding to ~/.bashrc:
echo 'export EDITOR=vim' >> ~/.bashrc && source ~/.bashrc
Bash Scripting Basics
A script is a text file of commands starting with a shebang. Always begin production scripts with set -euo pipefail so they exit on errors and undefined variables.
#!/usr/bin/env bash
set -euo pipefail
# variables and arguments
NAME="${1:-world}" # first arg, default "world"
echo "Hello, ${NAME}"
# conditionals
if [[ -f /etc/os-release ]]; then
echo "Found OS release file"
fi
# loops
for svc in nginx redis postgres; do
systemctl is-active --quiet "$svc" && echo "$svc up" || echo "$svc down"
done
# functions
backup() {
local src="$1" dst="$2"
tar -czf "$dst" "$src"
}
backup /srv/app "/backups/app-$(date +%F).tar.gz"
Quote your variables
Always wrap variable expansions in double quotes like "$file". Unquoted variables break on spaces and are the single most common source of script bugs. Run shellcheck to catch these automatically.
SSH
SSH gives you an encrypted shell on a remote machine. Prefer key-based authentication over passwords, and use ~/.ssh/config to save host aliases.
ssh-keygen -t ed25519 -C "you@example.com" # generate a modern key
ssh-copy-id deploy@server.example.com # install your public key
ssh deploy@server.example.com # connect
scp file.tar.gz deploy@server:/srv/ # copy a file over SSH
rsync -avz --progress ./dist/ deploy@server:/srv/app/ # efficient sync
# ~/.ssh/config
# Host prod
# HostName 203.0.113.10
# User deploy
# IdentityFile ~/.ssh/id_ed25519
# Then just: ssh prod
Practice Exercises
- Create a directory
/srv/app, give it ownerdeploy:devsand mode750, then verify withls -ldandstat. - Write a one-liner that finds the ten most frequent client IPs in an nginx access log using
awk,sort, anduniq. - Install nginx with your package manager, enable it to start on boot, then tail its logs live with
journalctl. - Use
ss -tulpnto identify which process is listening on port 80, then find and stop it by PID. - Write a bash script with
set -euo pipefailthat accepts a directory argument and creates a timestamped compressed tarball of it in/backups. - Generate an ed25519 SSH key, copy it to a test host, add a Host alias in
~/.ssh/config, and confirm you can log in without a password.