Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

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.

Levels: Beginner ~35 min read

Basic

What you will learn

  1. The four kinds of path an NGINX installation has — and who controls each.

  2. Reading nginx -V: the compiled-in DNA of your binary.

  3. The prefix ( -p) model — NGINX's own isolation trick, and Docker's.

  4. A guided tour of the config directory ( nginx.conf, mime.types, fastcgi_params, conf.d).

  5. 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 ./configure

--conf-path, --error-log-path, temp paths

partially — most have matching directives

Prefix-relative

the -p / --prefix you run with

logs/, conf/, relative roots

yes, by changing -p or writing absolute paths

Config-declared

your nginx.conf

root, access_log, pid

it is the config

Runtime state

NGINX as it runs

nginx.pid, temp files

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):

bash
$ 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-stream and --with-stream_ssl_preread_module — the stars of Modules 9 and 10 — plus --with-http_auth_request_module and --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:

bash
nginx -p /srv/edge/ -c conf/nginx.conf

and 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

nginx.conf

The main entry point: main context, events{}, http{}

Module 2

mime.types

~400 lines mapping extensions → Content-Type (an included types block)

Lesson 3.3

fastcgi_params

Environment headers for FastCGI apps ( SCRIPT_FILENAME, etc.)

Lesson 4.3

scgi_params, uwsgi_params

Same idea for SCGI/uWSGI

Lesson 4.3

conf.d/*.conf

Conventional drop-dir for extra http{}-level config

Lesson 2.6

modules/*.conf

One-line load_module files shipped with add-on packages

Module 15

sites-enabled/ (Debian-only)

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:

  1. Logs — logs/access.log, logs/error.log (relocatable via access_log / error_log directives).

  2. PID file — logs/nginx.pid, how scripts ( systemctl, init, or your own scripts) find the master to signal (Lesson 1.5).

  3. 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 than client_body_buffer_size

  • proxy_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:

bash
$ 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 failed

Syntax 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):

nginx
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:

bash
# 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

  1. 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_module present in both branches, if you run stable and mainline side by side (Lesson 1.2)?

bash
   nginx -V 2>&1
   nginx -V 2>&1 | tr ' ' '\n' | grep -c with-
  1. Tour a live instance. Every relative path resolves under the prefix — prove it by finding where the access log really is:

bash
   ls -la /var/log/nginx/
   tail -2 /var/log/nginx/access.log
  1. Watch 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:

bash
   ls -la /srv/edge/ | grep -E 'www|certs'
  1. (Optional, if you have root elsewhere) Compare a Debian-packaged nginx -V with 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 -V is 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 -p prefix 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 -t exercises 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.

Was this page helpful?

Mark this learning resource complete to track your learning progress.