Linux fundamentals for security, the working subset.
The 25 commands you will type every day, the five you will type in a hurry, and the file permissions that decide who owns the box.
Most "intro to Linux" material teaches you Linux as a Unix system. We are going to teach you Linux as a security workstation – what you actually do at the keyboard during recon, triage, and post-exploitation.
The shell, in one paragraph
The shell is a program that reads a line, runs it, and prints output. Three special characters do most of the work: | sends output to another program, > sends it to a file, and && chains two commands so the second only runs if the first succeeds. You can build anything from there.
The shell is not the commands
New arrivals conflate "bash" with "Linux". They are not the same thing, and the distinction matters the first time something breaks. The shell: bash, zsh, or whatever your prompt is running – is one specific program: an interpreter that reads a line of text and either runs it itself or hands it off. The commands you type are, in most cases, separate programs living on disk that the shell finds and launches for you.
Type ls and the shell doesn't "have" an ls feature built in: it searches every directory in the $PATH environment variable, in order, until it finds an executable file named ls, then forks and executes it:
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
$ which ls
/usr/bin/ls
$ type cd
cd is a shell builtin
That last line is the exception that proves the rule. A handful of commands – cd, pwd, alias, export, history – are builtins: code that lives inside the shell process, not a separate file on disk. cd has to be a builtin, because changing "the current directory" only means something if it changes the shell's own state; an external cd program would change its own working directory, exit, and leave your shell exactly where it started. type tells you which kind of thing you're actually invoking – builtin, alias, function, or file – and which does the narrower job of finding an external command's location on $PATH.
$PATH is searched in order, and if an attacker can write a malicious file named ls (or sudo, or curl) into a directory that appears earlier in your $PATH than the real one, your next keystroke runs their code instead of the real tool. This is PATH hijacking, a real privilege-escalation technique – never add a world-writable directory, or worse, . (the current directory), to your $PATH.
Swapping bash for zsh changes prompt behaviour and scripting syntax; it does not change what ls, grep, or chmod do, because those live outside the shell entirely. That is why the working vocabulary below transfers no matter which shell you land in.
The 25 commands
If you can use these without thinking, you can read 80% of real-world security workflows.
| Command | What it does | Used for |
|---|---|---|
ls -la | List files with details | Recon, permission audit |
cd / pwd | Move / where am I | Always |
cat / less | Read a file | Configs, logs |
grep -r | Search text recursively | Finding secrets, errors |
find | Search by name / size / time | Locating dropped files |
chmod / chown | Change permissions / owner | Hardening, lateral movement |
ps -ef | List processes | What is running |
top / htop | Live process view | Triage |
netstat -tulpn | Open ports + process | What is listening |
ss -tulpn | Modern replacement | Same |
curl -v | HTTP from CLI | API testing |
wget | Download files | Pulling tools |
ssh / scp | Remote shell / copy | Lateral movement |
tar | Pack / unpack archives | Exfil, deploy |
systemctl | Service control | Persistence, audit |
journalctl | Read systemd logs | Triage |
tail -f | Follow a log | Live monitoring |
history | What was typed | Forensics |
sudo | Run as root | Privilege use / audit |
id / whoami | Who am I, what groups | Recon on yourself |
uname -a | Kernel info | Exploit selection |
iptables -L | Firewall rules | Egress mapping |
dig / host | DNS lookup | Recon |
scp / rsync | Move files reliably | Backups, exfil |
tmux / screen | Persistent sessions | Long jobs |
Permissions in five minutes
Every file has three triplets of permissions: owner, group, everyone. Each triplet has three bits: read, write, execute.
$ ls -l /etc/shadow
-rw-r----- 1 root shadow 1832 May 6 14:01 /etc/shadow
^ owner=root group=shadow
rw- = read+write for owner
r-- = read for shadow group
--- = no access for everyone else
Reading the full ls -l line
The shadow example above is one field of interest. A real line has ten, and once you can read all ten without stopping to think, permission problems stop being mysterious.
$ ls -l deploy.sh
-rwxr-xr-- 1 dan devops 812 Jul 22 09:14 deploy.sh
| Field | Value | Meaning |
|---|---|---|
| Type | - | Regular file. d=directory, l=symlink, s=socket. |
| Owner perms | rwx | dan can read, write, and execute this file. |
| Group perms | r-x | Members of devops can read and execute, not write. |
| Other perms | r-- | Everyone else can only read it. |
| Link count | 1 | Number of hard links pointing at this inode. |
| Owner | dan | The user who owns the file. |
| Group | devops | The group that owns the file. |
| Size | 812 | Bytes. |
| Modified | Jul 22 09:14 | Last write time, not last read. |
| Name | deploy.sh | Symlinks show name -> target here instead. |
The two most common permission mistakes both live in that first character group: the execute bit missing on something you meant to run, or a file that should be private staying group- or world-readable – an SSH private key with anything looser than 600 will make ssh refuse to use it at all.
Numeric form: each triplet is a 0–7 digit. chmod 750 file means owner=rwx, group=r-x, world=none. The numbers are read=4, write=2, execute=1 – add them per triplet, so rwx = 4+2+1 = 7, r-x = 4+0+1 = 5, r-- = 4+0+0 = 4.
Symbolic notation: the other way to write it
Numeric mode replaces a whole triplet at once – you can't add one permission without knowing the target number for the rest. Symbolic mode edits in place instead, which is what you want for a one-off change on a file whose current permissions you haven't memorised. The grammar is who + operator + what: who is u/g/o/a (user, group, other, all); operator is +/-/= (add, remove, set exactly); what is any combination of r, w, x.
# Make a script executable for its owner only, leave everything else alone
$ chmod u+x deploy.sh
# Remove write access for the group
$ chmod g-w deploy.sh
# Set "other" to read-only, exactly, regardless of what it was before
$ chmod o=r deploy.sh
# Combine several changes in one call
$ chmod u+rwx,g+rx,o-rwx deploy.sh
Use numeric mode when you know the exact target state; use symbolic mode when adjusting one bit on a file you didn't create.
Chmod values you'll actually type
| Value | Meaning | Typical use |
|---|---|---|
600 | Owner rw, nobody else anything | SSH private keys, secrets, credential files |
644 | Owner rw, group/other read | Default for most non-executable files |
400 | Owner read-only | Files you want protected even from your own accidental overwrite |
700 | Owner rwx, nobody else anything | Personal scripts, the ~/.ssh directory |
750 | Owner rwx, group rx, other none | Service files shared within a team or group, not the world |
755 | Owner rwx, group/other rx | Default for scripts and executables meant to be run by anyone |
777 | Everyone rwx | Almost never correct: a routine finding in security audits |
A file with the SUID bit set runs as its owner, not as you. find / -perm -4000 -type f 2>/dev/null lists all of them. On a compromised box, this is where you look first.
The five "in a hurry" commands
When the alert is fresh and the senior engineer is in a meeting, these are the ones you reach for.
last -a | head -20: who logged in recently, from where.w: who is on the box right now, what are they running.ss -tunap: active sockets and the process behind each one.ls -lat /tmp /var/tmp /dev/shm | head: fresh files in the obvious dumping grounds.journalctl --since "1 hour ago" -p warning: recent warnings in the system log.
Pipes: the move that doubles your effective vocabulary
Two commands you have can be combined into a third. A few examples worth memorising:
# Top 10 processes by RSS memory
ps -eo pid,rss,cmd --sort=-rss | head -11
# Find every world-writable file under /etc
find /etc -type f -perm -o=w 2>/dev/null
# Recent failed SSH logins, grouped by source IP
journalctl -u ssh | grep "Failed" | awk '{print $NF}' | sort | uniq -c | sort -rn
Memorising these is not the point. The point is internalising the shape: list → filter → group → count. Once you see it, you will write your own.
When it breaks: two errors you'll see constantly
Two error messages account for a disproportionate share of "why isn't this working" moments. Both are diagnostic, not scary – each one tells you exactly where to look, if you know what it actually means.
"command not found"
This is not a permissions problem and the tool is not broken. It means the shell walked every directory in $PATH, in order, and found no executable file matching that name. Nothing more, nothing less.
$ nmap -sV target.local
bash: nmap: command not found
The first thing to check is not "is my system broken" – it's one of three much more boring possibilities, roughly in order of likelihood:
- Typo.
ifconfginstead ofifconfig. More common than anyone likes to admit. - Not installed. Confirm with your package manager:
dpkg -l | grep nmap(Debian/Ubuntu) orrpm -q nmap(RHEL/Fedora). If it's missing, install it. - Installed, but not on
$PATH. Tools installed viapip install --user,go install, or a manual download often land in~/.local/binor~/go/bin, which may not be on$PATHyet. Check withecho $PATH, and if the directory is missing, add it in your shell's startup file (~/.bashrcor~/.zshrc) and re-source it:source ~/.bashrc.
"Permission denied"
This one has two genuinely different causes that people constantly conflate, and reaching for sudo as a reflex fixes one of them by accident while masking the real problem in the other.
$ ./deploy.sh
bash: ./deploy.sh: Permission denied
Cause one: the execute bit isn't set. You can cat the file fine – you can read it – but reading and executing are separate permission bits, and a freshly downloaded or copied script often keeps its read bit while losing execute. Check with ls -l deploy.sh; if you don't see an x in the owner triplet, that's it. Fix: chmod +x deploy.sh.
Cause two: you genuinely don't have access. Wrong owner, wrong group, or a parent directory that blocks you – you need execute permission on every directory in the path just to traverse into a file, not only on the file itself. Check ownership with ls -l, check your own groups with id, and compare. If you're not in the group that owns the file, the fix is getting added to that group, not reflexively reaching for sudo.
A third, less common cause: the filesystem may be mounted noexec, which blocks execution of any file on it regardless of permission bits – common on /tmp in hardened environments as a deliberate anti-malware control. Check with mount | grep noexec. No amount of chmod fixes that.
None of this becomes instinct from reading it once: it comes from breaking things on purpose, on a machine where breaking things has no consequences. If you don't have one set up yet, our guide on building a home lab on one laptop walks through getting a throwaway Linux VM running in under an hour.
Frequently asked questions
What are the most important Linux commands for cybersecurity?
How do Linux file permissions work?
What is the SUID bit and why does it matter for security?
Which Linux commands are useful during incident response?
Daniel A. and Óscar S. run Breachfolio, a small independent site about security and AI. This article was drafted with AI assistance and reviewed by a person before it went live. We write from documentation, vendor sources and published research rather than from original lab benchmarks, and we link a source in the sentence that relies on it. How we work · About us