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.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.

Levels: Beginner ~35 min read

Basic

What you will learn

  1. The two NGINX branches — mainline and stable — and how to choose.

  2. Install path #1: your distribution's repository (and its hidden cost).

  3. Install path #2: the official nginx.org apt/yum repository (the default production choice on Debian/Ubuntu).

  4. Install path #3: a portable, supply-chain-verified extraction (no root).

  5. Install path #4: building from source (preview — full treatment in Module 15/16).

  6. 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.

bash
$ nginx -v                     # stable
nginx version: nginx/1.30.5

$ nginx -v                     # mainline
nginx version: nginx/1.31.6

2. Path #1 — your distribution's repository

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

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

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

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

bash
./configure --prefix=/opt/nginx --with-http_ssl_module --with-stream ...
make && sudo make install

You'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:

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

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

bash
~/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 --prefix model.

  • 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 -V is the first diagnostic on any unfamiliar box — Lesson 1.3 teaches you to read its entire output like a blood panel.

Was this page helpful?

Mark this learning resource complete to track your learning progress.