Linux Commands Every Developer Must Know

The Night a Server Nearly Drowned in Logs

2 AM on a Tuesday. PagerDuty screamed. A production server — the one handling payment webhooks — was at 97% disk. My stomach dropped. Customers couldn’t check out. Revenue was bleeding by the minute.

I SSH’d in, ran one command, and found the culprit in under ten seconds:

du -sh /var/log/* | sort -rh | head -5

A single debug log file had ballooned to 83 GB. Eighty-three. Someone had left verbose logging on after a deployment three weeks earlier, and nobody caught it. One truncate -s 0 later, disk dropped to 34%. Crisis over. Total time from alarm to fix: four minutes.

That night reinforced something I’d known but hadn’t respected enough: the terminal isn’t optional. It’s your fastest diagnostic tool, your sharpest scalpel, your emergency kit when GUIs won’t load because the box is out of memory. Over 96% of web servers worldwide run Linux. Every Docker container, every Kubernetes pod, every CI/CD runner — Linux kernel underneath. Even if you’re coding on a Mac or Windows machine, your stuff runs on Linux in production.

So here are 50 commands I actually use, organized the way I think about them. Not alphabetically. Not by some textbook taxonomy. By the problems they solve.

1-10. Files and Directories: The Basics You’ll Type 200 Times a Day

Look, I’m not going to pretend ls is exciting. But get the flags wrong and you’re staring at useless output when a deploy is failing. Get them right and you can diagnose permission issues, find recent changes, and spot oversized files without ever opening a file manager.

# 1. ls - List directory contents
ls -la              # Long format, show hidden files
ls -lhS             # Sort by size, human-readable
ls -lt              # Sort by modification time

# 2. cd - Change directory
cd /var/log         # Absolute path
cd ../config        # Relative path
cd ~                # Home directory
cd -                # Previous directory

# 3. pwd - Print working directory
pwd                 # Shows /home/anurag/projects

# 4. mkdir - Create directories
mkdir -p app/src/components   # Create nested directories

# 5. cp - Copy files and directories
cp file.txt backup.txt
cp -r src/ src-backup/       # Copy directory recursively

# 6. mv - Move or rename
mv old-name.js new-name.js
mv file.txt /tmp/            # Move to another location

# 7. rm - Remove files
rm file.txt
rm -rf node_modules/         # Remove directory recursively (use with caution!)

# 8. touch - Create empty file or update timestamp
touch newfile.txt
touch -t 202604121600 file.txt  # Set specific timestamp

# 9. ln - Create links
ln -s /usr/bin/python3 /usr/local/bin/python  # Symbolic link

# 10. find - Search for files
find . -name "*.py" -type f                    # Find Python files
find /var/log -mtime -7 -name "*.log"          # Modified in last 7 days
find . -size +100M                             # Files larger than 100MB
find . -name "*.tmp" -exec rm {} \;            # Find and delete

A few flags worth burning into memory. ls -lt is probably my most-typed variant — when something breaks after a deploy, you want to know what changed recently. find . -size +100M saved me during that 2 AM incident. And cd -? Criminally underused. It bounces you back to wherever you just were. Faster than retyping a path.

One thing I’ll warn about: rm -rf has no undo. No recycle bin. No confirmation unless you alias it. I’ve watched a junior dev accidentally nuke a config directory on a staging server. Lesson learned the hard way for the entire team. If you’re doing anything destructive on production, maybe pipe through xargs echo first to preview what you’re about to delete.

11-20. Text Processing: Where the Terminal Actually Gets Interesting

Here’s where Linux starts pulling ahead of everything else. Reading files, searching through codebases, slicing CSVs, transforming config — you can do in a single piped command what would take a Python script fifteen lines to accomplish.

# 11. cat - Display file contents
cat config.yaml
cat file1.txt file2.txt > merged.txt    # Concatenate files

# 12. less - Paginated file viewer
less /var/log/syslog          # Navigate with j/k, search with /

# 13. head / tail - View start or end of files
head -n 20 app.log            # First 20 lines
tail -n 50 app.log            # Last 50 lines
tail -f app.log               # Follow live output (streaming logs)

# 14. grep - Search text with patterns
grep -r "TODO" src/                    # Recursive search in directory
grep -n "error" app.log                # Show line numbers
grep -i "warning" app.log              # Case insensitive
grep -c "404" access.log               # Count matches
grep -v "DEBUG" app.log                # Invert match (exclude DEBUG)

# 15. awk - Column-based text processing
awk '{print $1, $4}' access.log        # Print columns 1 and 4
awk -F: '{print $1}' /etc/passwd       # Custom delimiter
df -h | awk '$5 > "80%" {print $0}'    # Disks over 80% usage

# 16. sed - Stream editor for text transformation
sed 's/old/new/g' file.txt                     # Replace all occurrences
sed -i 's/localhost/0.0.0.0/g' config.yaml     # In-place edit
sed -n '10,20p' file.txt                       # Print lines 10-20

# 17. sort and uniq - Sort and deduplicate
sort access.log | uniq -c | sort -rn | head    # Top repeated lines
cat urls.txt | sort -u                          # Sort and deduplicate

# 18. wc - Word, line, and byte count
wc -l src/**/*.py             # Count lines of Python code
find . -name "*.js" | wc -l   # Count JavaScript files

# 19. cut - Extract columns from text
cut -d',' -f1,3 data.csv      # Extract columns 1 and 3 from CSV
echo $PATH | cut -d: -f1-3    # First 3 PATH entries

# 20. diff - Compare files
diff file1.txt file2.txt
diff -u old.conf new.conf     # Unified format (like git diff)

grep deserves its own paragraph because it’s honestly the command I use more than anything besides ls and cd. Knowing grep -rn "pattern" src/ by heart means you can search an entire codebase in milliseconds. I used to reach for VS Code’s search panel. Now I don’t bother — the terminal is faster, works over SSH, and doesn’t choke on massive repos the way some editors do.

tail -f is another one that changed how I debug. Streaming logs in real time while reproducing a bug in another terminal window? Absolutely essential. And if you pair it with grep --line-buffered, you can filter the stream live. Watching only error lines fly by while ignoring thousands of INFO messages — that’s a productivity unlock most devs don’t discover until year three.

awk and sed might look intimidating. They probably are, at first. But you don’t need to master every feature. Learn the three or four patterns above and you’ll handle 90% of what comes up. The rest you can look up when you need it.

21-26. Process Management: Keeping Your Servers Alive (and Killing What Shouldn’t Be)

Production debugging almost always starts with “what’s consuming all the CPU?” or “why is this port already in use?” Process management commands answer both questions immediately.

# 21. ps - List processes
ps aux                          # All processes with details
ps aux | grep node              # Find Node.js processes

# 22. top / htop - Real-time process monitor
top -o %MEM                     # Sort by memory usage
htop                            # Interactive process viewer (install: apt install htop)

# 23. kill - Terminate processes
kill 12345                      # Graceful termination (SIGTERM)
kill -9 12345                   # Force kill (SIGKILL)
killall node                    # Kill all processes by name

# 24. bg / fg / jobs - Job control
Ctrl+Z                          # Suspend current process
bg                              # Resume in background
fg                              # Bring to foreground
jobs                            # List background jobs

# 25. nohup - Run process that survives logout
nohup python server.py &        # Run in background, persist after logout

# 26. systemctl - Manage systemd services
sudo systemctl start nginx
sudo systemctl enable nginx     # Start on boot
sudo systemctl status nginx
sudo systemctl restart nginx
journalctl -u nginx -f          # Stream service logs

Quick distinction that trips people up: kill sends SIGTERM by default, which asks a process to shut down gracefully. kill -9 sends SIGKILL, which doesn’t ask — it executes. Use SIGTERM first, always. Only reach for -9 when the process is truly hung. I’ve seen developers default to kill -9 on database processes and corrupt data files. Not fun.

htop isn’t installed by default on most distributions, but it’s worth the two-second apt install htop. Color-coded, interactive, mouse-friendly. Once you’ve used it, regular top feels like reading a spreadsheet printed on a receipt.

And here’s a pattern I rely on constantly: ps aux | grep node to find rogue Node.js processes that didn’t die after a failed deploy. Happens more than you’d think, especially on staging environments where nobody bothers with proper process managers.

27-34. Networking: Because Every Bug Is a Network Bug (Eventually)

I’m only half-joking. APIs timing out, SSL certs expiring, DNS not resolving, ports blocked by firewalls — networking commands are the ones you wish you’d learned before you needed them at 11 PM on a Friday.

# 27. curl - Transfer data from URLs
curl https://api.example.com/users               # GET request
curl -X POST -H "Content-Type: application/json" \
  -d '{"name":"Anurag"}' https://api.example.com/users
curl -o file.zip https://example.com/file.zip     # Download file
curl -I https://example.com                        # Headers only

# 28. wget - Download files
wget https://example.com/dataset.csv
wget -r -np https://example.com/docs/             # Recursive download

# 29. ssh - Secure shell
ssh user@192.168.1.100
ssh -i ~/.ssh/mykey.pem ubuntu@ec2-instance
ssh -L 8080:localhost:3000 user@server             # Port forwarding

# 30. scp - Secure copy over SSH
scp file.txt user@server:/home/user/
scp -r ./dist/ user@server:/var/www/html/

# 31. netstat / ss - Network statistics
ss -tulnp                       # Show listening ports with process info
ss -s                           # Summary statistics

# 32. ping - Test connectivity
ping -c 4 google.com            # Send 4 packets

# 33. dig / nslookup - DNS lookup
dig byteyogi.com
dig +short byteyogi.com A       # Just the IP address

# 34. ip - Network interface configuration
ip addr show                    # Show all interfaces and IPs
ip route show                   # Show routing table

curl is practically a Swiss Army knife. Quick API testing without Postman, downloading files, checking response headers, verifying redirects. I keep a text file of curl commands for common API calls on every project I work on. Faster than switching to a GUI tool, and it works inside SSH sessions where Postman obviously can’t.

ssh -L (port forwarding) is something I wish someone had shown me years ago. Running a database on a remote server that only accepts local connections? Forward port 5432 through your SSH tunnel and connect from your machine like it’s localhost. No need to expose database ports to the internet. Probably the most underappreciated ssh flag out there.

ss -tulnp replaced netstat on modern Linux, and it’s faster. When you get “address already in use” errors, this command tells you exactly which process is squatting on the port. I’d guess I run it at least twice a week.

35-38. Disk and Storage: Finding What’s Eating Your Space

Remember that 2 AM story from the intro? All four of these commands were part of my diagnostic toolkit that night. Disk problems tend to be silent until they’re catastrophic — your app just starts throwing write errors, databases refuse to accept new rows, log rotation fails quietly.

# 35. df - Disk space usage
df -h                           # Human-readable format

# 36. du - Directory size
du -sh /var/log                 # Total size of directory
du -sh * | sort -rh | head -10  # Top 10 largest items in current dir

# 37. mount - Mount filesystems
mount | grep sda                # Show mounted partitions
sudo mount /dev/sdb1 /mnt/data

# 38. lsblk - List block devices
lsblk                          # Show disk layout and partitions

df -h gives you the 10,000-foot view: how much space is left on each mounted filesystem. du -sh * piped through sort -rh lets you drill down directory by directory, hunting for the space hog. Between these two commands, you can almost always find the problem in under a minute.

Pro tip from painful experience: if df shows a disk as full but du doesn’t account for all the space, you’ve probably got deleted files still being held open by a running process. Run lsof +D /var/log | grep deleted to find them. Restarting the offending process releases the space. Took me embarrassingly long to figure that one out the first time.

39-42. Permissions and Users: The Stuff That Blocks Every First Deploy

Nobody thinks about file permissions until their deployment script fails with “Permission denied.” Then it’s suddenly the most important topic in the universe. chmod and chown seem straightforward, but the octal notation trips up almost everyone at least once.

# 39. chmod - Change file permissions
chmod 755 script.sh             # rwxr-xr-x
chmod +x deploy.sh              # Add execute permission
chmod -R 644 public/            # Recursive permission change

# 40. chown - Change file ownership
sudo chown www-data:www-data /var/www/html -R

# 41. whoami / id - Current user info
whoami                          # anurag
id                              # uid, gid, groups

# 42. sudo - Execute as superuser
sudo apt update
sudo -u postgres psql           # Run as a different user

Here’s the cheat sheet I keep in my head: 7 = read + write + execute, 5 = read + execute, 4 = read only. So 755 means the owner can do everything, and everyone else can read and execute but not write. 644 means the owner can read and write, everyone else can only read. For scripts you need to run, chmod +x is usually all you need. Don’t overthink it.

sudo -u postgres psql is one I reach for when working with PostgreSQL on servers where the postgres user owns the database. You can run commands as any user without actually logging in as them. Works the same way for service accounts, application users, whatever.

43-44. Compression: Moving Files Without Melting Your Bandwidth

Backups. Deployments. Log archives. Data transfers. Compression commands aren’t glamorous, but you’ll reach for them weekly.

# 43. tar - Archive files
tar -czf backup.tar.gz ./project/            # Create gzipped archive
tar -xzf backup.tar.gz                       # Extract gzipped archive
tar -xzf backup.tar.gz -C /opt/              # Extract to specific directory

# 44. zip / unzip
zip -r project.zip ./project/
unzip project.zip -d ./extracted/

The tar flags are notoriously easy to forget. Here’s how I remember them: create, extract, z for gzip, f for file. So -czf is “create a gzipped file” and -xzf is “extract a gzipped file.” Mnemonic helped me stop Googling tar syntax every single time.

When you’re transferring large directories over SSH, tar piped through ssh can be faster than scp -r because it streams a single archive rather than opening separate connections per file:

tar czf - ./project/ | ssh user@server "tar xzf - -C /opt/"

That one-liner has saved me hours on slow connections.

45-50. System Info and Utilities: The Commands You Didn’t Know You Needed

These don’t fit neatly into other categories, but each one pulls its weight. Some you’ll use daily (env, export). Others you’ll use rarely but critically (uname when debugging OS-specific build failures, crontab when setting up scheduled tasks).

# 45. uname - System information
uname -a                        # Full system info
cat /etc/os-release             # OS version details

# 46. uptime - System uptime and load
uptime                          # 16:00 up 45 days, load average: 0.52, 0.48, 0.43

# 47. free - Memory usage
free -h                         # Human-readable memory info

# 48. env / export - Environment variables
env                             # Show all environment variables
export NODE_ENV=production
echo $PATH

# 49. crontab - Schedule recurring tasks
crontab -e                      # Edit cron jobs
# Run backup every day at 2 AM:
# 0 2 * * * /home/anurag/scripts/backup.sh >> /var/log/backup.log 2>&1

# 50. xargs - Build and execute commands from stdin
find . -name "*.log" | xargs rm                    # Delete all .log files
cat urls.txt | xargs -I {} curl -s -o /dev/null -w "%{http_code} {}\n" {}
echo "file1 file2 file3" | xargs -n 1 gzip        # Compress each file

xargs is sneakily powerful. It takes output from one command and feeds it as arguments to another. Without it, you’d write a lot more bash loops. That find ... | xargs rm pattern is something I probably run three times a month when cleaning up temp files or stale build artifacts.

crontab syntax looks like someone spilled their keyboard, I know. Five fields: minute, hour, day of month, month, day of week. A star means “every.” So 0 2 * * * means “at minute 0, hour 2, every day, every month, every day of the week.” Once that clicks, it’s actually not bad. There are also online crontab generators if you don’t want to memorize it. No shame in that.

free -h paired with uptime gives you an instant health check of any server. Memory pressure and load average in two commands. When my monitoring dashboards go down (and yes, monitoring systems fail too, deliciously ironic), these two are my fallback.

Piping It All Together: Where the Real Power Lives

Individual commands are useful. Piped commands are where Linux becomes something no GUI can match. The | operator takes the output of one command and feeds it as input to the next. Chain three or four together and you’ve got a custom data-processing pipeline assembled in seconds.

Here are one-liners I’ve used in actual production situations. Not contrived examples — real problems that needed real solutions fast.

# Find the 10 largest files in a directory tree
find /var -type f -exec du -h {} + 2>/dev/null | sort -rh | head -10

# Monitor a log file for errors in real time
tail -f /var/log/app.log | grep --line-buffered "ERROR"

# Count unique IP addresses in an access log
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20

# Kill all zombie processes
ps aux | awk '$8 ~ /Z/ {print $2}' | xargs -r kill -9

# Disk usage breakdown of current directory, sorted
du -sh */ 2>/dev/null | sort -rh

That first one — finding the largest files — is essentially what I ran during the 2 AM incident, just pointed at /var instead of /var/log. It runs find to locate every file, pipes their sizes through sort to rank them, and head to show only the top 10. Clean, fast, no external tools needed.

The zombie process killer is one I keep bookmarked. Zombie processes shouldn’t normally pile up, but on long-running servers with poorly written daemon scripts, they sometimes do. That one-liner finds them, extracts their PIDs, and force-kills them in a single pass.

And the IP counting one-liner? I’ve used it to identify abusive scrapers hammering an API. Five minutes of access logs through that pipe, and you’ve got a list of the top 20 offenders sorted by request count. Pipe it into your firewall rules and the problem’s solved.

Building Your Own Command Arsenal

Fifty commands is a lot to absorb in one sitting, and I’m not pretending you’ll memorize them all from reading a blog post. I didn’t. Took me years of daily terminal use, one emergency at a time. But here’s what accelerated things for me:

Ditch the GUI for one week. Browse files with ls and cd. Search code with grep. View logs with tail and less. Monitor processes with htop. You’ll be slower at first, obviously. By day three or four, your fingers start remembering.

SSH into servers instead of using web consoles. AWS, GCP, DigitalOcean — they all have browser-based terminals, and they’re all terrible. Laggy, no clipboard integration, no scrollback. A proper SSH session from your local terminal is faster and more capable in every way.

Build shell aliases for your most-typed commands. I’ve got alias ll='ls -la', alias gs='git status', and probably thirty others in my .bashrc. Small optimizations compound.

Read man pages. Actually read them. I know, I know. They’re dense. But man grep taught me about -A and -B flags (show lines after/before a match) that I now use almost daily. Five minutes with a man page is often worth more than thirty minutes Googling StackOverflow answers that may be outdated.

Here’s the Dare

I’ve given you 50 commands. You probably already know twenty of them. Maybe thirty. Pick five that you’ve never used — or used but never understood the flags for — and force yourself to use them this week. Not in a sandbox. In your real workflow.

Maybe that’s awk to parse a CSV instead of writing a Python script. Maybe it’s ss -tulnp next time you get a port conflict instead of Googling “how to find what’s using port 3000.” Maybe it’s find . -size +100M when your Docker host runs low on disk instead of guessing which container to prune.

Five commands. One week. That’s the challenge.

And if you want to go further, try building a multi-pipe bash one-liner every day for a month. A single command that solves a real problem. Share the good ones with your team. Nothing earns dev cred faster than casually dropping a three-pipe one-liner in a Slack channel that saves someone twenty minutes of manual work.

The terminal rewards daily practice. Start today.

Leave a Comment

Your email address will not be published. Required fields are marked with an asterisk.