strace is one of those tools that feels like debugging magic the first time you use it. You run it on a process, and suddenly you see exactly what it’s doing at the system call level — every file it tries to open, every permission error, every network connection. No source code reading required.
Here’s the thing: when an app fails with “permission denied” or “file not found,” the error message tells you what failed, but not why. strace shows you the whole chain. It’s invaluable when you’re staring at code you didn’t write or dealing with closed-source binaries.
What’s a System Call?
A system call is when a userspace application asks the kernel to do something it can’t do itself — open a file, make a network connection, allocate memory, spawn a process. strace intercepts these calls and logs them.
$ strace -e openat ls /tmp 2>&1 | head -n 20openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3openat(AT_FDCWD, "/tmp", O_RDONLY|O_CLOEXEC|O_DIRECTORY) = 3Each line is one system call. The number at the end (like = 3) is the file descriptor returned by the kernel, or an error code if it failed.
Basic Usage: Trace Everything
$ strace ./myappexecve("./myapp", ["./myapp"], [/* 58 vars */]) = 0brk(NULL) = 0x55555555a000arch_prctl(ARCH_SET_FS, 0x55555555a880) = 0mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffff7ff7000...That’s noisy. Thousands of lines. Most of it is runtime initialization that you don’t care about. Usually you want to filter.
Pattern 1: Trace Only File Operations
Find what files an app is trying to open:
$ strace -e openat,open,read,write ./myapp 2>&1 | grep -E "(openat|open)" | head -n 20openat(AT_FDCWD, "/etc/passwd", O_RDONLY) = 3openat(AT_FDCWD, "/var/log/app.log", O_WRONLY|O_CREAT|O_APPEND, 0644) = 4openat(AT_FDCWD, "/config.yaml", O_RDONLY) = -1 ENOENT (No such file or directory)That last line? Permission error. The app tried to open /config.yaml and got “No such file or directory.” Now you know where to look.
Pattern 2: Trace System Calls for a Running Process
Your app is already running and doing weird things. Don’t restart it — attach to it:
$ sudo strace -p 1234 -e trace=open,openat,read,writeAdd -f to follow child processes too:
$ sudo strace -p 1234 -f -e trace=filePattern 3: Trace Network Connections
See what network calls an app makes:
$ strace -e trace=network ./myappsocket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0bind(3, {sa_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr("0.0.0.0")}, 16) = 0listen(3, 128) = 0Your app creates a TCP socket, sets options, binds to port 8080, and starts listening. Useful for debugging network issues.
Pattern 4: Find Permission Errors
App won’t start, complains about permissions, but the error is vague. Trace it:
$ strace -e openat ./app 2>&1 | grep EACCESopenat(AT_FDCWD, "/etc/app.conf", O_RDONLY) = -1 EACCES (Permission denied)Now you know: it’s trying to read /etc/app.conf but doesn’t have permission. Fix the file permissions, done.
Pattern 5: Trace a Specific Syscall with Arguments
See the actual arguments to a function:
$ strace -e write ./app 2>&1 | head -n 10write(1, "Hello, World!\n", 14) = 14write(2, "Error: something failed\n", 24) = 24The middle part ("Hello, World!\n") is what got written. The 1 is stdout, 2 is stderr. Useful for understanding what data is actually flowing through the system.
Pattern 6: Get Statistics, Not Details
Running a web server through strace produces millions of lines. Get a summary instead:
$ strace -c ./myapp% time seconds usecs/call calls errors syscall------ ----------- ----------- --------- --------- ---------------- 20.45 0.001234 23 54 mmap 15.23 0.000921 18 52 mprotect 12.45 0.000751 15 49 open 8.90 0.000537 11 47 read ...Shows which syscalls are slowest or called most often. Great for spotting performance problems.
Real-World Scenarios
Debugging a startup failure:
$ strace -f ./service 2>&1 | grep -E "ENOENT|EACCES" | head -n 5Understanding database connection issues:
$ strace -e trace=network node app.js 2>&1 | grep -E "connect|bind"Finding which config file an app actually reads:
$ strace -e openat ./app 2>&1 | grep "\.conf\|\.yaml\|\.json" | grep -v ENOENTThings to Know
1. strace adds overhead. It slows down your app significantly. For quick debugging, that’s fine. For performance profiling, use perf instead.
2. You often need sudo. If you’re tracing a process you don’t own or tracing certain syscalls, root is required.
3. The output is huge. Filter with -e to see only what you care about. Piping to grep is your friend.
4. Errors aren’t always fatal. A syscall returning ENOENT (file not found) doesn’t mean the process crashed. Apps often try multiple paths.
When This Saves You
You’re integrating with a closed-source tool and it’s not working. You can’t read the source. strace is your X-ray. Run it, look for errors, and follow the evidence. It beats hours of blind debugging.
That’s the power of strace — it turns black boxes transparent.