1.2 Installing NGINX: Every Path That Matters
Hands-on: you will install (or at least deeply understand) four different installation paths and know when each is right.
Basic
What you will learn
The two NGINX branches — mainline and stable — and how to choose.
Install path #1: your distribution's repository (and its hidden cost).
Install path #2: the official
nginx.orgapt/yum repository (the default production choice on Debian/Ubuntu).Install path #3: a portable, supply-chain-verified extraction (no root).
Install path #4: building from source (preview — full treatment in Module 15/16).
How to verify any NGINX package cryptographically, by hand.
1. Two branches, one binary name
NGINX ships in two parallel versions at all times:
Branch | Current version | Cadence | Gets | Use it when |
|---|---|---|---|---|
Mainline | 1.31.x | active development | new features, all bug fixes | you want the newest capabilities (HTTP/3 polish, latest modules) and can live on the edge |
Stable | 1.30.x | branched from mainline yearly | critical bug fixes only | production conservatism: nothing changes unless it must |
Which to run? The nginx.org documentation itself is pragmatic: mainline is generally recommended (it's where all fixes land first), while stable exists for change-averse environments. Nothing stops you from keeping both installed side by side — which is exactly the comparison setup of Module 16.
$ nginx -v # stable
nginx version: nginx/1.30.5
$ nginx -v # mainline
nginx version: nginx/1.31.62. Path #1 — your distribution's repository
sudo apt install nginx # Debian/Ubuntu
sudo dnf install nginx # Fedora/RHEL family
Pros: one command, automatic security updates with the rest of your OS, works with distro tooling (
systemctl status nginx).
Cons — and they're structural:
Version lag. Distros freeze the NGINX version at their release date. Debian stable ships NGINX versions that are years old — fine for pure proxying, painful when you need modern TLS behavior or HTTP/3.
Different defaults. Distro builds compile with different paths and patches (Debian enables
sites-enabled/includes; nginx.org builds don't). Half the stale StackOverflow answers on the internet are people mixing the two layouts. Pick one layout and know which one you're in (Lesson 1.3 teaches you to tell instantly fromnginx -V).Compile differences. Distro builds may omit modules the nginx.org build includes (the official stable build ships
--with-stream,--with-http_v3_module, and more — check the next lesson's output).
3. Path #2 — the official nginx.org repository (production default)
This is the flow
setup.sh (or Ansible, or cloud-init) automates if you have root — and the one Path #3 below reproduces by hand. On Debian/Ubuntu:
# 1. install the prerequisites
sudo apt install curl gnupg2 ca-certificates lsb-release
# 2. fetch and inspect the nginx.org signing key
curl -fsSL https://nginx.org/keys/nginx_signing.key -o /tmp/nginx_signing.key
gpg --show-keys --with-fingerprint /tmp/nginx_signing.key
# MUST print one of these fingerprints:
# 8540 A6F1 8833 A80E 9C16 53A4 2FD2 1310 B49F 6B46
# 573B FD6B 3D8F BC64 1079 A6AB ABF5 BD82 7BD9 BF62
# 3. register it with apt
echo "deb [signed-by=/usr/share/keyrings/nginx_signing.asc] \
https://nginx.org/packages/debian $(lsb_release -cs) nginx" \
| sudo tee /etc/apt/sources.list.d/nginx.list
# 4. install
sudo apt update && sudo apt install nginx
nginx -v # -> nginx/1.30.x (stable) or .../mainline if you used that repo
Why go to this trouble when
apt install nginx
exists? Because with this repo you get: current versions, the canonical file layout (the one all nginx.org documentation assumes), first-party bug fixing, and
nginx-module-* add-on packages (njs, geoip, image-filter) built against
your exact binary — dynamic modules are version-sensitive, as Module 15 will make painfully clear.
4. Path #3 — a portable installation (no root, no system changes)
Sometimes you need NGINX on a machine you can't administer — a laptop, a CI container, a locked-down corporate host. Write yourself a small installer —
install-nginx.sh — and it can install NGINX
without root by performing, by hand, the same steps
apt performs (
fetch-nginx.sh does the fetching):
1. fetch the nginx.org signing key → PIN its fingerprint
2. fetch the apt InRelease file → verify its PGP signature
3. read the signed SHA-256 of the Packages index
4. download the Packages index → verify its SHA-256
5. download nginx .debs → verify each SHA-256 against it
6. dpkg -x (extract, don't install) → ~/nginx/stable/
~/nginx/mainline/If any step fails the script aborts — tampered packages cannot pass. What you end up with is a fully functional nginx binary tree that never touched
/usr/sbin,
/etc, or
/var:
A launcher in
~/bin/ is a thin
exec wrapper:
$ cat ~/bin/nginx
#!/usr/bin/env bash
# portable nginx (stable) — extracted, not installed
exec "$HOME/nginx/stable/usr/sbin/nginx" "$@"5. Path #4 — building from source (preview)
./configure --prefix=/opt/nginx --with-http_ssl_module --with-stream ...
make && sudo make installYou'd compile when you need: a custom module statically linked, a stripped-down feature set (embedded appliances), unusual platforms, or a debug build (Module 16 does exactly this with
--with-debug). The price: you own the build, the updates, and the CVE tracking. Full treatment in Module 15 (dynamic modules) and Module 16 (debug binary).
6. Verification: the 60-second pre-flight for ANY install
Regardless of path, run these before trusting an NGINX:
nginx -v # the version it claims
nginx -V # version + compile-time DNA (next lesson)
nginx -t # does the shipped default config even parse?
ps aux | grep [n]ginx # is something already running? whose binary?
ss -tlnp | grep :80 # who owns the ports you're about to want?The same pre-flight applies to the application runtimes that later modules proxy to (PHP-FPM, Node, Python/uWSGI, gRPC, njs). The example stack this series was verified on:
nginx -v tops the list; each runtime gets its own version check:
nginx (stable) : nginx version: nginx/1.30.5
nginx (mainline): nginx version: nginx/1.31.6
php : 8.4.24
node : v24.21.0
python : Python 3.12.14
grpcio : yes
uwsgi : real binary
njs module : yes (ngx_http_js_module.so ngx_stream_js_module.so ...)Try it yourself
Start from the §6 pre-flight against your own install:
Then experiment with the two branches, if you installed both (they can even run simultaneously — instances are isolated by prefix and port):
~/nginx/stable/usr/sbin/nginx -v 2>&1 # stable
~/nginx/mainline/usr/sbin/nginx -v # mainline
Try it yourself: write
fetch-nginx.sh end to end — the six steps above, in order. The exercise teaches more practical supply-chain hygiene than most security training: fingerprint pinning, detached signature verification, hash-pinned downloads, and graceful failure. You should be able to answer:
what exactly does step 3 protect against that step 2 doesn't?
Answer to that question
Step 2 (PGP signature on
InRelease) proves the
repository metadata came from the key holder. Step 3–4 (SHA-256 of the Packages index against the signed Release file) proves the
package list wasn't swapped after signing. Step 5 proves the
individual .deb bytes match that list. Each link covers the next attack surface: signed-but-stale metadata, or a mirror serving mismatched files.
Q&A
Q1. Why would you install both branches?
Answer
To make branch differences observable rather than theoretical — behavior comparisons in later modules (e.g. HTTP/3 defaults in Module 9,
ssl_reject_handshake in Module 6) can be run against both binaries. Also, since they're isolated by prefix (Lesson 1.3), there's no conflict.
Q2. Your Debian 12 box has
nginx from Debian's repo. A vendor doc says "add
include /etc/nginx/conf.d/*.conf;" — works or not, and why might it
still mislead you?
Answer
Works — both layouts have
conf.d. But Debian's build
also ships
sites-enabled/ includes, so configs may load from places the vendor doc doesn't know about.
nginx -T (Lesson 2.6) is the definitive answer for "what is actually loaded".
Q3. In the portable (Path #3) install, why extract with
dpkg -x instead of
dpkg -i?
Answer
-i
installs (needs root, mutates the system package database, runs maintainer scripts).
-x merely unpacks the file tree into a directory — perfect for running with an explicit prefix, zero side effects.
Q4. True or false: mainline is a beta channel.
Answer
False. Mainline is the primary development line and receives all bug fixes first; stable is the conservative annual branch. Neither is "beta" — both are release-quality; they differ in change appetite.
Takeaways
Stable = frozen feature set + critical fixes; mainline = the living branch. nginx.org generally recommends mainline; choose stable when change-aversion is a hard requirement.
Distro packages trade currency for convenience; the official repo trades one setup step for currency + canonical layout + module compatibility.
Portable/extracted installs are a real deployment pattern (containers, immutable hosts), not a hack — powered by the
--prefixmodel.Cryptographic verification is a chain: pinned fingerprint → signed metadata → hashed artifacts. Any install you can't trace through that chain is an install you're trusting blindly.
nginx -Vis the first diagnostic on any unfamiliar box — Lesson 1.3 teaches you to read its entire output like a blood panel.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.