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

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.

Levels: Advanced ~30 min read

Advanced

What you will learn

  1. The pipeline and what each stage produces.

  2. ./configure anatomy: the flag families and the ones that matter.

  3. Paths, prefixes, and the real build receipts.

  4. When a source build is the right answer (and its permanent costs).


1. The pipeline

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

Four stages, four artifacts:

Stage

Artifact

Notes

./configure

objs/Makefile, ngx_auto_config.h

every decision is made HERE — the build is just cooking

make

objs/nginx

the binary, statically carrying your choices

make modules

objs/ngx_*.so

dynamic modules only (Lesson 15.3's target)

make install

prefix tree

copies + spools — trivially replaceable with cp

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

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

text
>> 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 says ngx_brotli @ a71f931.)

  • make modules , not make — 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 install would lay down a prefix tree; we just cp the two .so files into the module dir load_module already 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 ( make modules), keep the distro binary (this lesson's choice)

Need --with-debug (Module 16)

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 ./configure in the build tree and read its output — the final summary lists every path and module the build chose. Compare it to nginx -V of 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 and ngx_auto_config.h it 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-module for .sos).

  • The recommended pattern: distro binary + custom-built dynamic modules — same-version source, mirrored flags, make modules, cp the .so.

  • A custom binary is a standing commitment (CVEs, upgrades, module re-checks); make it for debug builds and patches, not for fashion.

Was this page helpful?

Mark this learning resource complete to track your learning progress.