15.2 Building NGINX from Source
Three commands and ninety seconds: ./configure, make, make install. The skill is knowing what configure decides on your behalf — those choices become your binary’s contract with the world.
Advanced
What you will learn
The pipeline and what each stage produces.
./configureanatomy: the flag families and the ones that matter.Paths, prefixes, and the real build receipts.
When a source build is the right answer (and its permanent costs).
1. The pipeline
$ curl -fsSL -o nginx-1.30.5.tar.gz https://nginx.org/download/nginx-1.30.5.tar.gz
$ tar xzf nginx-1.30.5.tar.gz && cd nginx-1.30.5
$ ./configure --with-compat --with-http_ssl_module ...
$ make # or: make modules (for dynamic .so only)
$ make install # or: cp objs/*.so wherever load_module looksFour stages, four artifacts:
Stage | Artifact | Notes |
|---|---|---|
|
| every decision is made HERE — the build is just cooking |
|
| the binary, statically carrying your choices |
|
| dynamic modules only (Lesson 15.3's target) |
| prefix tree | copies + spools — trivially replaceable with
|
The brotli build in Lesson 15.3 is exactly this pipeline with
make modules only — we never replaced the packaged binary; we produced a
.so to load into it.
2. Configure anatomy
The flag families, by consequence:
Path flags — where the binary will look, compiled in as defaults (Lesson 1.3's anatomy, now from the other side):
--prefix=/usr/local/nginx # everything else defaults relative to this
--conf-path= --pid-path= --error-log-path= ...
--http-client-body-temp-path= ... # the exact /var/cache/nginx paths the
# isolated-prefix and container setups
# kept overriding!Every module conf in this series that redirected temp paths was fighting THESE defaults. A source build makes the right defaults baked in — one of the quiet arguments for custom builds in unusual environments.
Feature flags (
--with-*) — modules compiled IN. The packaged build's list (your
nginx -V receipt) is long:
--with-http_ssl_module --with-http_v2_module --with-http_v3_module --with-stream --with-http_auth_request_module --with-http_secure_link_module… — you have used every one of those in this series. A minimal build (
./configure with nothing) compiles a shockingly small nginx — no TLS, no proxy tuning surface, no status.
The flag list IS the feature list.
Removal flags (
--without-*) — shrink deliberately:
--without-http_rewrite_module (no PCRE dep!),
--without-
http_gzip_module,
--without-http_proxy_module for tiny static servers. Rarely worth it except for appliances.
Toolchain flags —
--with-cc-opt,
--with-ld-opt (the packaged build's hardening flags ride here), and the special ones:
--with-debug (Module 16's microscope) and
--with-compat (Lesson 15.3's module-compatibility dial).
Module adders —
--add-module=PATH (static compile-in) vs
--add-dynamic-module=PATH (build a
.so). The dynamic form is the modern default.
3. The receipts
What the build actually looked like in this module (real):
>> downloading nginx-1.30.5.tar.gz ...
>> cloning google/ngx_brotli (recursive: deps/brotli rides along) ...
>> ./configure (mirroring the packaged binary's flags + our module) ...
>> make modules ...
'objs/ngx_http_brotli_filter_module.so' -> '.../nginx-modules/'
'objs/ngx_http_brotli_static_module.so' -> '.../nginx-modules/'Details worth internalizing:
Same version as the target binary (1.30.5) — not "close enough," THE version. Module compatibility starts here.
git clone --recursive— ngx_brotli vendors the brotli C library as a submodule; a plain clone builds nothing. (Supply-chain note: you now own THAT pin too. Note the commit hash in your build log — ours saysngx_brotli @ a71f931.)make modules, notmake— we needed only the.so; building the whole binary would work but waste a minute and imply a binary we didn't intend to install.Install = copy —
make installwould lay down a prefix tree; we justcpthe two.sofiles into the module dirload_modulealready reads (modules/, a prefix-relative convention).
4. When to build from source
The honest decision table:
Reason | Verdict |
|---|---|
Need a dynamic module built |
Yes — but build the MODULE (
|
Need
| Yes — a side-by-side debug binary, same version, distinct prefix |
Need a flag your distro omits | Maybe — first check whether the feature ships as a dynamic module instead |
Need to patch C behavior | Reluctantly — and with a written rebase plan per CVE |
"Sources are cleaner than packages" | No — you just volunteered for CVE-watching duty |
The permanent costs of a custom
binary: you track nginx CVEs yourself (distro
apt upgrade no longer covers you), you own the upgrade choreography, and every module must be re-checked per rebuild. That's why the recommended pattern is
distro binary + custom modules wherever possible — the best of both bills.
Try it yourself
cd ~/nginx/build/nginx-1.30.5 && make modules— assuming you parked §2’s unpacked source under~/nginx/build/(any directory works; re-running is safe and fast — the configured tree is cached).Re-run
./configurein the build tree and read its output — the final summary lists every path and module the build chose. Compare it tonginx -Vof the packaged binary.cd ~/nginx/build/nginx-1.30.5 && ./configure --help | less— the full flag surface. Skim it once; you'll grep it for years.
Q&A
Q:
make -j$(nproc)
? A: Yes — nginx builds linearly without it; parallel make turns minutes into tens of seconds. (A module-only target is already fast.)
Q: Where do prerequisites come from? A:
./configure finds PCRE (or PCRE2), zlib, OpenSSL via headers on the system (
--with-cc-opt=-I... if not in default paths). A typical build host has
pcre2.h and
openssl/ssl.h — check yours before configuring; failures point at exactly the missing library.
Q: Can I have two nginx versions side by side? A: Yes — distinct prefixes (that's the whole
prefix-per-instance philosophy, and how Module 16's debug binary will coexist with the packaged one). Distinct pid files and ports, naturally.
Q: What is
objs/ngx_auto_config.h
telling me? A: Every feature-detection result from configure — the C equivalent of the "what does this build support" receipt. When something behaves differently across builds, diff this file first.
Takeaways
All decisions happen in
./configure— the Makefile it emits andngx_auto_config.hit writes ARE the build.Flag families: paths (compiled-in defaults your configs have been fighting), features (
--with-*= the feature list), removals, toolchain, and module adders (--add-dynamic-modulefor.sos).The recommended pattern: distro binary + custom-built dynamic modules — same-version source, mirrored flags,
make modules,cpthe.so.A custom binary is a standing commitment (CVEs, upgrades, module re-checks); make it for debug builds and patches, not for fashion.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.