The Linux File System Is a Living API — And Most People Don't Know It
A system investigator's field notes from hunting through /proc, /etc, /dev, and beyond

Linux doesn't hide how it works. Every secret about your running system — active TCP connections, kernel memory layout, routing decisions, user identities, hardware interrupt counters — is exposed as a readable file somewhere in the filesystem. The moment I realized this, I stopped treating Linux as an operating system and started treating it as a self-documenting machine.
This is what I found while hunting.
Finding 1: /proc/net/route — Your Routing Table Is a Hex File
Most people check their routing table with ip route or the older route -n. What they don't know is that both of these tools are just pretty-printers reading the same raw file at /proc/net/route.
Here's what the raw file looks like:
Iface Destination Gateway Flags Metric Mask
eth0 A8000415 00000000 0001 0 7FFFFFFF
eth0 00000000 A9000415 0003 0 00000000
Every value here is little-endian hexadecimal. That A9000415 gateway? Convert it byte-by-byte from the right: 15.04.00.A9 → 21.4.0.169. This is 169.254.0.21 — a link-local address, which tells you this is running inside a container or cloud VM using a metadata gateway.
Why it matters: Routing decisions happen before packets leave your machine. Understanding this file means you can detect misconfigured gateways, double-check container networking, or audit VPN routes without any extra tools — just cat and a hex converter. When your traffic is disappearing, this file tells you where Linux is sending it.
Finding 2: /proc/self/maps — Every Process Carries a Memory Map
Every running process exposes its complete virtual memory layout at /proc/<pid>/maps. When you run cat /proc/self/maps, you're looking at the memory map of the cat process itself — reading its own brain.
Here's a real excerpt:
56306fa42000-56306fa44000 r--p 00000000 00:11 49 /usr/bin/cat
56306fa44000-56306fa49000 r-xp 00002000 00:11 49 /usr/bin/cat ← executable code
56306fa4d000-56306fa6e000 rw-p 00000000 00:00 0 [heap]
7ed539400000-7ed539428000 r--p 00000000 00:11 48 /usr/lib/x86_64-linux-gnu/libc.so.6
7ed539749000-7ed53974a000 r--p 00000000 00:00 0 [vvar]
The permission flags tell the whole story: r--p is read-only, r-xp is executable code, rw-p is writable data. Notice that libc.so.6 (the C standard library) is mapped in — every C program carries it. The [vvar] segment is a kernel-to-userspace shared memory region that allows gettimeofday() to work without a system call, making it near-instant.
The insight: Linux never actually "loads" a program fully into RAM. It maps file regions into virtual memory and pages them in on-demand. This is why a 100MB binary starts in milliseconds. The entire loading strategy of your OS is readable in this one file.
Finding 3: /etc/nsswitch.conf — The Arbitrator of All Name Resolution
When your application says connect("google.com"), Linux has to figure out who to ask. That decision is made entirely by /etc/nsswitch.conf, a file most developers have never opened.
hosts: files dns
This single line means: first check /etc/hosts, then ask DNS. The order is not a default — it's a configuration. You can change it. You can add mdns for multicast DNS. You can put dns before files. Organizations with internal hostnames and split-horizon DNS live and die by this file.
On the system I explored, the full chain was:
passwd: files→ user lookup from/etc/passwdonly, no LDAPhosts: files dns→ local override, then nameservernetgroup: nis→ NIS (Network Information Service) for network groups
Why it exists: Different environments need different resolution strategies. A container might only need files. An enterprise machine might need ldap for users and mdns for service discovery. This file is the traffic cop that routes those lookups to the right place.
Finding 4: /etc/shadow — Passwords Were Never in /etc/passwd
Most people assume passwords live in /etc/passwd. They did — in the 1970s. Today /etc/passwd has an x in the password field, which literally means "go look in /etc/shadow instead."
# /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
# /etc/shadow (root-readable only)
root:*:20553:0:99999:7:::
daemon:*:20553:0:99999:7:::
The * in shadow means login is disabled (no valid password hash). The number 20553 is the last password change date counted in days since Jan 1, 1970. The 99999 means the password never expires. The 7 means warn the user 7 days before expiry.
The security insight: /etc/passwd is world-readable because programs need it to map UIDs to names. Putting actual password hashes there would expose them to every unprivileged process. Shadow was created specifically to separate identity (passwd) from authentication secrets (shadow). Permission levels: /etc/passwd is 644, /etc/shadow is 640 or 000 — only root or the shadow group gets in.
Finding 5: /proc/1/status — PID 1 Is the Soul of the Container
In any Linux system, Process ID 1 is special — it's the ancestor of all processes. On a bare-metal server, PID 1 is usually systemd or init. On the container I explored, PID 1 was named process_api. This alone is a fingerprint: it told me immediately I was inside an application container, not a full OS.
Name: process_api
Pid: 1
PPid: 0 ← no parent — this is the root
Uid: 0 0 0 0 ← running as root (real, effective, saved, filesystem)
VmRSS: 10488 kB ← ~10MB of actual RAM in use
Threads: 4
CapEff: 00000000a82c35fb ← capability bitmask
Seccomp: 0
The CapEff (effective capabilities) field is a bitmask of Linux capabilities — the fine-grained permission system that replaced "root does everything." Even though UID is 0, the capability set is restricted, meaning even root in this container can't do things like CAP_SYS_ADMIN (mount filesystems) or CAP_NET_ADMIN (configure network interfaces).
The insight: Modern containers don't truly run as "root" in the traditional sense. They run with a reduced capability set, and /proc/<pid>/status exposes exactly what those restrictions are. Security auditors read this file. Attackers read this file. You should too.
Finding 6: /dev/null, /dev/zero, /dev/urandom — Devices That Are Just Concepts
The /dev directory traditionally holds device files — interfaces to hardware. But some of the most important entries in /dev have no physical hardware behind them. They're kernel concepts materialized as files.
$ stat /dev/null
File: /dev/null
Size: 0 Device type: 1,3 Access: (0666/crw-rw-rw-)
/dev/null(major:minor1,3): A write-only void. Anything written disappears. Reads return EOF immediately. This is howcommand > /dev/null 2>&1silences output — you're sending data to a kernel blackhole./dev/zero(major:minor1,5): Produces infinite null bytes on read. Used to wipe disks (dd if=/dev/zero of=/dev/sda), create zero-filled files, or initialize memory regions./dev/urandom: A cryptographically secure pseudorandom number generator backed by the kernel's entropy pool (hardware events, interrupt timing, network packet timing). Everyssh-keygen, every TLS session, every token generation reads from here.
Why this design matters: The Unix philosophy says "everything is a file." By modeling kernel services as files, any program that can read/write files can use them — no special API, no library, no privileges needed (for null/zero/urandom). This is architectural elegance.
Finding 7: /proc/sys/net — Kernel Network Behavior Is Runtime-Configurable
The entire TCP/IP stack behavior of Linux is tunable through /proc/sys/net — and changes take effect immediately, with no reboot.
/proc/sys/net/ipv4/ip_forward → 0 (this machine won't route packets)
/proc/sys/net/ipv4/tcp_syn_retries → 3 (retry SYN 3 times before giving up)
/proc/sys/net/core/somaxconn → 1024 (max pending connections per socket)
ip_forward = 0 means this host drops packets not addressed to it — it won't act as a router. Set it to 1 and the machine instantly becomes a router. This is exactly what Docker does when you docker run a container with port mappings: it writes 1 to this file and adds iptables rules.
somaxconn = 1024 is the maximum backlog of TCP connections waiting to be accept()ed by an application. Under heavy load, if your web server is slow to call accept(), the kernel starts silently dropping new connections at 1024. Many production performance issues trace back to this value being too low.
The insight: Linux isn't a static OS. It's a reconfigurable runtime. The /proc/sys tree is the live control panel of the kernel, and tools like sysctl are just wrappers around reading and writing these files.
Finding 8: /etc/environment vs /etc/profile — Two Different Kinds of "Global"
When you want a variable to be available system-wide, you have two choices — and they behave very differently.
/etc/environment is a flat key=value file, not a shell script:
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
It's read by PAM (Pluggable Authentication Modules) and applies to every session, login or not, including GUI apps launched from a display manager. It doesn't support variable expansion or conditionals — just static assignments.
/etc/profile is an actual shell script:
if [ -d /etc/profile.d ]; then
for i in /etc/profile.d/*.sh; do
[ -r "\(i" ] && . "\)i"
done
fi
It's sourced only in login shells. Notice how it loops over /etc/profile.d/ — this is a plugin pattern. Packages drop their own .sh files there instead of editing a shared file, avoiding conflicts. Java drops its JAVA_HOME there. CUDA drops its paths there.
The critical difference: If you set a variable in /etc/profile and wonder why your cron job doesn't see it — that's why. Cron doesn't launch a login shell by default. /etc/environment would have worked.
Finding 9: /proc/net/tcp — Active Connections Without netstat
The file /proc/net/tcp contains every active TCP connection and listening socket on the system — raw, in hex.
sl local_address rem_address st
3: 00000000:07E8 00000000:0000 0A ← LISTEN on port 0x07E8 = 2024
8: A8000415:07E8 C439040A:E612 01 ← ESTABLISHED connection
State 0A = LISTEN, state 01 = ESTABLISHED. The port 0x07E8 = 2024 in decimal. The remote IP C439040A decoded (little-endian): 0A.04.39.C4 = 10.4.57.196. The remote port E612 = 58898.
In one file, without any tools, you can see: what ports are open, who is connected, and what state those connections are in.
Why this matters for security: This file is the ground truth. Tools like netstat and ss read it. But if an attacker patches netstat to hide a connection (a classic rootkit trick), the connection still shows up in /proc/net/tcp — because the kernel writes it directly, and the kernel can't be lied to by user-space tool replacement.
Finding 10: /etc/systemd/ — The Directory That Replaced init.d
The /etc/systemd/ directory is the nerve center of the modern Linux boot and service management system. Unlike the old /etc/init.d/ scripts (which were just shell scripts with no dependency awareness), systemd uses declarative unit files.
/etc/systemd/
├── journald.conf ← controls log persistence, compression, size limits
├── logind.conf ← controls seat/session management
├── network/ ← static network configs
├── system/ ← enabled service unit overrides
├── system.conf ← global systemd defaults (CPU accounting, timeouts)
└── user.conf ← per-user manager defaults
journald.conf is particularly interesting. The default journal is stored in /run/log/journal/ — which lives in RAM and is wiped on reboot. Only if you create /var/log/journal/ does it become persistent. Many administrators don't know their logs vanish on every restart.
system.conf contains DefaultTimeoutStopSec=90s by default — the time systemd waits for a service to gracefully stop before sending SIGKILL. If your application takes longer than 90 seconds to shut down cleanly, systemd kills it brutally. This is a common source of data corruption in databases that need time to flush writes.
The architectural insight: systemd treats service management, logging, network, login sessions, and timers as a unified system with dependency graphs. /etc/systemd/ is where you customize that graph.
Conclusion: The Filesystem Is the Interface
The deeper you go into Linux, the more you realize it wasn't designed to hide things from you — it was designed to expose them. Every abstraction has a file behind it. Every behavior has a knob. Every running state has a proc entry.
The tools we use daily (ip, netstat, ps, top, sysctl) are largely just interpreters that read these files and present them in human-readable form. Understanding the source material makes you faster at debugging, harder to deceive, and fundamentally better at reasoning about what a system is actually doing versus what it appears to be doing.
The Linux filesystem isn't just storage. It's an API to the kernel itself — and it's been open this whole time.


