1.3 Anatomy of an Installation
You will learn to read nginx -V like a radiologist reads an X-ray, and to locate every file an NGINX owns on any machine in 60 seconds.
Basic
What you will learn
The four kinds of path an NGINX installation has — and who controls each.
Reading
nginx -V: the compiled-in DNA of your binary.The prefix (
-p) model — NGINX's own isolation trick, and Docker's.A guided tour of the config directory (
nginx.conf,mime.types,fastcgi_params,conf.d).Runtime state: logs, PID file, and temp areas — including the classic trap that bites a custom-prefix install on first boot.
1. Four kinds of path
Every path NGINX touches falls into exactly one of these buckets. When something "can't find a file", identifying the bucket is half the diagnosis:
Kind | Chosen by | Examples | Can a config override it? |
|---|---|---|---|
Compiled-in defaults | whoever ran
|
| partially — most have matching directives |
Prefix-relative | the
|
| yes, by changing
|
Config-declared | your
|
| it is the config |
Runtime state | NGINX as it runs |
| location inherited from the above |
2.
nginx -V: the binary's DNA
nginx -v prints the version;
nginx -V (capital) prints the version plus
every decision the packager made at compile time. Here is the real output from the official nginx.org stable binary (line-wrapped for the page):
$ nginx -V
nginx version: nginx/1.30.5
built by gcc 14.2.0 (Debian 14.2.0-19)
built with OpenSSL 3.5.6 7 Apr 2026 (running with OpenSSL 3.5.7 9 Jun 2026)
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx \
--modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/run/nginx.pid --lock-path=/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
--user=nginx --group=nginx --with-compat --with-file-aio --with-threads \
--with-http_addition_module --with-http_auth_request_module \
--with-http_dav_module --with-http_flv_module --with-http_gunzip_module \
--with-http_gzip_static_module --with-http_mp4_module \
--with-http_random_index_module --with-http_realip_module \
--with-http_secure_link_module --with-http_slice_module \
--with-http_ssl_module --with-http_stub_status_module \
--with-http_sub_module --with-http_v2_module --with-http_v3_module \
--with-mail --with-mail_ssl_module --with-stream \
--with-stream_realip_module --with-stream_ssl_module \
--with-stream_ssl_preread_module ...Decoding the fields that matter:
--prefix=/etc/nginx— the default prefix when you don't pass-p. Relative paths in the default config resolve against it.--conf-path/--pid-path/--error-log-path— defaults used when the config doesn't say otherwise. The config always wins where a directive exists (pid,error_log,access_log).--http-*-temp-path— the five temp areas (client bodies, proxy, fastcgi, uwsgi, scgi). Each has an override directive — see §5.--user=nginx --group=nginx— the identity workers drop to when the master starts as root (Lesson 1.4 explains the privilege dance).--with-…— modules compiled in. Anything listed here is always available; anything NOT listed must come from a dynamic.so(Module 15). Note this official build includes--with-http_v3_module(QUIC),--with-streamand--with-stream_ssl_preread_module— the stars of Modules 9 and 10 — plus--with-http_auth_request_moduleand--with-http_secure_link_module, which Module 11 exploits.built with OpenSSL X (running with Y)— the TLS library it was compiled against vs. the one it loaded. When these differ a lot, cipher behavior can differ from docs (Module 6 checks this).TLS SNI support enabled— required for name-based virtual hosting over HTTPS (Module 6).
3. The prefix model: one binary, sixteen NGINXes
Run
nginx -p /some/prefix/ and relative paths resolve there instead of the compiled
--prefix.
logs/nginx.pid,
logs/error.log, an included
conf/nginx.conf, a
root html — all resolve under your chosen directory.
That single mechanism is how one binary can run 16 isolated instances with zero root:
nginx with its own prefix and config — a second instance in one line:
nginx -p /srv/edge/ -c conf/nginx.confand every further instance gets the same treatment, each on its own port range. It is also how official NGINX Docker images run (
-g "daemon off;" plus a prefix), and how you'd run a second instance on a shared host without touching
/etc/nginx.
4. The config directory, file by file
What a stock
/etc/nginx (and, in a per-instance layout, each
conf/) contains:
File | Role | Taught in |
|---|---|---|
| The main entry point: main context,
| Module 2 |
| ~400 lines mapping extensions →
| Lesson 3.3 |
| Environment headers for FastCGI apps (
| Lesson 4.3 |
| Same idea for SCGI/uWSGI | Lesson 4.3 |
| Conventional drop-dir for extra
| Lesson 2.6 |
| One-line
| Module 15 |
| Symlink farm for vhosts | Lesson 2.6 (and why it's not "the NGINX way") |
The key insight:
nginx.conf
is not special to NGINX — it is just the file named by
-c
/
--conf-path
. Everything else (
mime.types,
conf.d/*.conf) is included by
convention via
include directives inside
nginx.conf. Delete the includes and NGINX wouldn't know those files exist. Lesson 2.6 turns this into a full config-organization strategy.
5. Runtime state — and the temp-path trap
A running instance writes state in three places:
Logs —
logs/access.log,logs/error.log(relocatable viaaccess_log/error_logdirectives).PID file —
logs/nginx.pid, how scripts (systemctl, init, or your own scripts) find the master to signal (Lesson 1.5).Temp areas — where partial request bodies and in-flight proxied responses spill to disk when they exceed their in-memory buffers:
client_body_temp— uploads larger thanclient_body_buffer_sizeproxy_temp/fastcgi_temp/uwsgi_temp/scgi_temp— backend responses larger than their respective buffers
And here is the trap, hit
for real the very first time you run a packaged binary from a custom prefix. The binary compiles temp paths to
/var/cache/nginx/…. Because those are
absolute compiled defaults, they do
not relocate with
-p. Result:
$ nginx -t
nginx: the configuration file ... syntax is ok
2026/09/22 20:11:43 [emerg] 2258#2258: mkdir() "/var/cache/nginx/client_temp" \
failed (2: No such file or directory)
nginx: configuration file ... test failedSyntax was fine —
nginx -t failed because it
actually creates temp dirs while testing. The fix is to redirect each temp area inside the config (this stanza appears in every example
nginx.conf):
http {
client_body_temp_path tmp/client_body;
proxy_temp_path tmp/proxy;
fastcgi_temp_path tmp/fastcgi;
uwsgi_temp_path tmp/uwsgi;
scgi_temp_path tmp/scgi;
...
}6. Privileges: root master, unprivileged workers
On a system install, the master runs as root (it must: ports < 1024, PID files in
/run, log ownership) and immediately drops workers to the compiled
--user=nginx identity. In an unprivileged install, master and workers simply all run as you. Either way the
architecture is identical — Lesson 1.4 dissects it — and the observable difference is just UID columns:
# system install (root) # unprivileged (same user)
USER PID COMMAND USER PID COMMAND
root 812 nginx: master ... z 2397 nginx: master ...
nginx 813 nginx: worker ... z 2449 nginx: worker ...
nginx 814 nginx: worker ... z 2450 nginx: worker ...Try it yourself
Read your binary's DNA, and answer from the output (not memory): which temp paths does it default to? Which modules are compiled in? Is
--with-http_v3_modulepresent in both branches, if you run stable and mainline side by side (Lesson 1.2)?
nginx -V 2>&1
nginx -V 2>&1 | tr ' ' '\n' | grep -c with-Tour a live instance. Every relative path resolves under the prefix — prove it by finding where the access log really is:
ls -la /var/log/nginx/
tail -2 /var/log/nginx/access.logWatch the prefix isolate instances. A common trick is to symlink shared content or certs into the prefix instead of copying — do that, then find the symlinks and follow them:
ls -la /srv/edge/ | grep -E 'www|certs'(Optional, if you have root elsewhere) Compare a Debian-packaged
nginx -Vwith the official repo's. Note every difference you can find; there are at least five.
Q&A
Q1. An instance started with
-p /srv/edge/ and a config containing only
error_log logs/error.log; writes its error log where? What if the config contains
no
error_log at all?
Answer
/srv/edge/logs/error.log — relative paths resolve against the runtime prefix. With no
error_log directive, NGINX falls back to the compiled
--error-log-path (
/var/log/nginx/error.log here),
not the prefix — one of the few cases where the compiled default beats the prefix.
Q2. Your
nginx -t fails with
mkdir() "/var/cache/nginx/client_temp" failed — but only on the staging box, not production. Why?
Answer
Production has
/var/cache/nginx (created by the package install, owned by the NGINX user). Staging runs the same binary without the package's directory-creation steps (extracted/copy-only deploy, wrong user). Fix at the source: set
client_body_temp_path (and friends) in config so you don't depend on packaged directory state.
Q3. Why can 16 instances share one binary but never read each other's configs?
Answer
Each invocation names its own
-p (prefix) and
-c (config). All relative paths — config, logs, pid, temp, roots — resolve per-prefix. The binary is stateless; the prefix is the state.
Q4. Where does an uploaded 30 MB request body live while NGINX decides what to do with it?
Answer
In memory while under
client_body_buffer_size (typically 8–16 KB), then spilled to a file under
client_body_temp_path. Buffer tuning for this pipeline is Module 9 material; the
path you now control from this lesson.
Takeaways
Four path kinds — compiled defaults, prefix-relative, config-declared, runtime state — and one diagnostic question: whose choice was this path?
nginx -Vis a full confession of build-time decisions; reading it tells you the layout, the modules, and the expected file locations of any machine you land on.The
-pprefix model is the unit of isolation: it's how one binary serves many isolated instances and how Docker images run NGINX. Relative paths follow the prefix; compiled absolute defaults do not.Config parsing is separate from temp-dir provisioning —
nginx -texercises both, which is why it can fail with a filesystem error on a config that parses cleanly.The only files NGINX "knows" natively are the ones you include; everything else (
mime.types,conf.d/) is convention.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.