How to Build NGINX from Source on Ubuntu 26.04, 24.04 and 22.04

Last updated Wednesday, April 29, 2026 6:57 am Joshua James 8 min read

Third-party modules, custom TLS builds, and HTTP/3 experiments are the main reasons to build NGINX from source on Ubuntu instead of using the repository package. A source build lets you decide which modules are compiled in, which dynamic modules are available, and which upstream branch you track.

The workflow below downloads the current nginx.org source tarball, verifies its PGP signature, compiles NGINX against Ubuntu libraries, installs it to Ubuntu-friendly paths, creates a systemd service, and gives you a repeatable update and removal path.

Source builds do not receive automatic security updates from Ubuntu. Subscribe to NGINX security notices or check the official NGINX download page regularly, then rebuild when nginx.org publishes a release you need.

These steps target Ubuntu 26.04 LTS, 24.04 LTS, and 22.04 LTS. The build workflow is the same across these releases, but OpenSSL behavior differs: Ubuntu 26.04 currently provides OpenSSL 3.5.x, while Ubuntu 24.04 and 22.04 provide OpenSSL 3.0.x and use NGINX’s HTTP/3 compatibility layer.

Do not install this source build over an existing APT-managed NGINX service unless you intentionally plan to replace it. The commands below write to /usr/sbin/nginx, /etc/nginx, and /usr/lib/nginx/modules. For package-managed updates, use install NGINX on Ubuntu or install NGINX mainline on Ubuntu instead.

Build NGINX from Source on Ubuntu

Start from a terminal on the Ubuntu system where you want the source build installed. Server and minimal installs often lack compiler packages, so install the build dependencies before downloading the tarball.

Update Ubuntu Before Building NGINX

Refresh APT metadata so dependency packages come from the current Ubuntu repositories.

sudo apt update

These commands use sudo. If your account does not have administrative access yet, follow the guide on how to add a user to sudoers on Ubuntu before continuing.

Install NGINX Source Build Dependencies

Install the compiler, download tools, signature-verification tools, and libraries used by the configure flags in this guide.

sudo apt install build-essential curl gnupg libpcre2-dev libssl-dev zlib1g-dev libgd-dev

These packages provide the core pieces required for this build:

  • build-essential: GCC, G++, make, and standard headers for compiling software.
  • curl: Downloads the source tarball, signature file, and public keys from nginx.org.
  • gnupg: Verifies the detached PGP signature before extraction.
  • libpcre2-dev: Provides PCRE2 support for rewrite rules, location matching, and --with-pcre-jit.
  • libssl-dev: Provides OpenSSL headers for HTTPS, HTTP/2, and optional HTTP/3 builds.
  • zlib1g-dev: Provides compression support for gzip and related modules.
  • libgd-dev: Builds the image filter module as a dynamic module.

The libgd-dev package comes from Ubuntu’s universe component. If APT cannot find it on a minimal or customized system, enable Universe first using the Ubuntu Universe repository guide.

Optional XSLT Module Dependencies

Install these packages only if you plan to enable the XSLT module with --with-http_xslt_module or --with-http_xslt_module=dynamic.

sudo apt install libxml2-dev libxslt1-dev

The legacy GeoIP module depends on the deprecated libGeoIP library and is not included here. For GeoIP2 deployments, build and maintain the matching third-party module against the same NGINX source version.

Download and Verify NGINX Source Code

Create a Source Build Directory

Keep the source tree in your home directory so compilation does not require root access.

mkdir -p "$HOME/src/nginx"
cd "$HOME/src/nginx"

Select the Current Mainline Tarball

The next command resolves the current mainline source tarball from nginx.org. Keep using the same terminal session after this step because later commands reuse the NGINX_TARBALL variable.

NGINX_TARBALL="$(curl -fsSL https://nginx.org/en/download.html | tr '<' '\n' | sed -n '/h4>Mainline version/,/\/table>/p' | grep -Eo 'nginx-[0-9]+\.[0-9]+\.[0-9]+\.tar\.gz' | head -n1)"
test -n "$NGINX_TARBALL" || { echo "Could not detect the current mainline tarball."; exit 1; }
printf '%s\n' "$NGINX_TARBALL"

As of April 2026, nginx.org returned:

nginx-1.29.8.tar.gz

Mainline is the best fit when you build from source for newer protocol work or module testing. If you prefer the stable branch, replace Mainline version with Stable version in the command above before downloading.

Download the Tarball and Signature

Download both the source tarball and its detached PGP signature from the exact version selected above.

curl -fLO "https://nginx.org/download/$NGINX_TARBALL"
curl -fLO "https://nginx.org/download/$NGINX_TARBALL.asc"
ls -1 "$NGINX_TARBALL" "$NGINX_TARBALL.asc"

A successful download leaves both files in the build directory:

nginx-1.29.8.tar.gz
nginx-1.29.8.tar.gz.asc

Verify the NGINX Source Signature

Import the current NGINX release-signing public keys into a temporary local keyring, then verify the tarball signature before extracting the source.

mkdir -p "$HOME/src/nginx/gnupg"
chmod 700 "$HOME/src/nginx/gnupg"
export GNUPGHOME="$HOME/src/nginx/gnupg"

for key in arut pluknet sb thresh; do
  curl -fLO "https://nginx.org/keys/${key}.key"
done

gpg --import arut.key pluknet.key sb.key thresh.key
gpg --verify "$NGINX_TARBALL.asc" "$NGINX_TARBALL"

The signer can change between releases. The important result is Good signature plus a key fingerprint you can compare against the NGINX PGP public keys page.

gpg: Signature made Tue 07 Apr 2026 07:44:50 PM AWST
gpg:                using RSA key D6786CE303D9A9022998DC6CC8464D549AF75C0A
gpg: Good signature from "Sergey Kandaurov <s.kandaurov@f5.com>" [unknown]
Primary key fingerprint: D678 6CE3 03D9 A902 2998  DC6C C846 4D54 9AF7 5C0A

The trust warning that GnuPG may print after a good signature means the key is not certified in your personal keyring. Treat a missing Good signature line as a stop condition.

Extract and Enter the Source Tree

Extract the verified tarball and move into the matching source directory.

tar -xzf "$NGINX_TARBALL"
cd "${NGINX_TARBALL%.tar.gz}"
test -f auto/configure && printf 'source tree ready: %s\n' "$PWD"

Relevant output includes the source path:

source tree ready: /home/your-user/src/nginx/nginx-1.29.8

Configure the NGINX Source Build

The ./configure script checks available libraries and writes a Makefile with the paths and modules you choose. Run ./configure --help inside the source tree if you need the full option list for a custom build.

CategoryOptionUse
Paths--prefix=/pathSets the base path used by relative defaults.
--sbin-path=/pathSets the installed NGINX binary path.
--conf-path=/pathSets the main nginx.conf path.
--modules-path=/pathSets the directory for dynamic modules.
HTTP and TLS--with-http_ssl_moduleEnables HTTPS support.
--with-http_v2_moduleEnables HTTP/2 support.
--with-http_v3_moduleEnables HTTP/3 support when the TLS stack supports QUIC.
--with-http_sub_moduleEnables the sub_filter response-substitution directives.
--with-http_gzip_static_moduleServes pre-compressed .gz assets.
--with-http_realip_moduleRestores client IPs behind proxies and CDNs.
--with-http_stub_status_moduleAdds the lightweight status module.
Dynamic modules--with-http_image_filter_module=dynamicBuilds the image filter module as a loadable .so file.
--with-http_xslt_module=dynamicBuilds the XSLT module as a loadable module when the optional dependencies are installed.
--add-dynamic-module=/pathBuilds a third-party module as a dynamic module.
Performance--with-threadsEnables thread pools for blocking disk I/O.
--with-file-aioEnables asynchronous file I/O support.
--with-pcre-jitEnables PCRE2 JIT for regular-expression matching.

Configure NGINX for Ubuntu Paths

This configure command installs the binary at /usr/sbin/nginx, stores configuration under /etc/nginx, writes dynamic modules under /usr/lib/nginx/modules, and uses www-data for worker processes.

./configure \
  --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=/var/lock/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=www-data \
  --group=www-data \
  --with-threads \
  --with-file-aio \
  --with-http_ssl_module \
  --with-http_v2_module \
  --with-http_sub_module \
  --with-http_gzip_static_module \
  --with-http_realip_module \
  --with-http_stub_status_module \
  --with-http_image_filter_module=dynamic \
  --with-pcre-jit

Relevant output includes the detected libraries and install paths:

checking for PCRE2 library ... found
checking for OpenSSL library ... found
checking for zlib library ... found
checking for GD library ... found
checking for GD WebP support ... found
creating objs/Makefile

Configuration summary
  + using threads
  + using system PCRE2 library
  + using system OpenSSL library
  + using system zlib library

A source build installs the upstream sample configuration. It does not automatically create Ubuntu’s packaged sites-available/sites-enabled layout or a conf.d include. Before following a separate NGINX configuration guide, inspect /etc/nginx/nginx.conf and adapt file paths to the include layout you actually use.

Compile and Install NGINX

Compile the source tree. The -j"$(nproc)" form uses the available CPU cores while keeping the command copy-safe.

make -j"$(nproc)"

Install the compiled binary, default configuration files, and dynamic modules into the paths selected during configuration.

sudo make install

Relevant install output includes the binary and dynamic module destinations:

cp objs/nginx '/usr/sbin/nginx'
cp conf/nginx.conf '/etc/nginx/nginx.conf.default'
cp objs/ngx_http_image_filter_module.so '/usr/lib/nginx/modules/ngx_http_image_filter_module.so'

Create Runtime Directories

The temporary cache paths in the configure command are not created by the build. Create them before starting the service.

sudo mkdir -p /var/cache/nginx/client_temp \
  /var/cache/nginx/proxy_temp \
  /var/cache/nginx/fastcgi_temp \
  /var/cache/nginx/uwsgi_temp \
  /var/cache/nginx/scgi_temp

sudo chown -R www-data:www-data /var/cache/nginx

Load the Image Filter Module

If you plan to use image filter directives, load the dynamic module from the configured module directory. The guard keeps the line from being added twice if you rerun the command.

MODULE_LINE='load_module /usr/lib/nginx/modules/ngx_http_image_filter_module.so;'
grep -qxF "$MODULE_LINE" /etc/nginx/nginx.conf || sudo sed -i "1i$MODULE_LINE" /etc/nginx/nginx.conf

Skip this line if you removed --with-http_image_filter_module=dynamic from the configure command.

Create a systemd Service for NGINX

Create a systemd unit so Ubuntu can start, stop, reload, and enable the source-built NGINX service like a packaged daemon.

sudo tee /etc/systemd/system/nginx.service > /dev/null <<'EOF'
[Unit]
Description=NGINX web server
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload
ExecStop=/usr/sbin/nginx -s quit
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF

Reload systemd, start NGINX, and enable it for future boots.

sudo systemctl daemon-reload
sudo systemctl start nginx
sudo systemctl enable nginx

Confirm the service is running and enabled.

systemctl is-active nginx
systemctl is-enabled nginx

A working service returns:

active
enabled

Verify the Source-Built NGINX Install

Check NGINX Configuration Syntax

Run the built-in syntax check before serving traffic or reloading the service after edits.

sudo nginx -t

Expected output:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Confirm NGINX Build Flags

Check the installed version and configure arguments. Use sudo nginx -V if your shell does not include /usr/sbin in PATH.

nginx -V

A current Ubuntu 26.04 source build returned:

nginx version: nginx/1.29.8
built with OpenSSL 3.5.5 27 Jan 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=/var/lock/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=www-data --group=www-data --with-threads --with-file-aio --with-http_ssl_module --with-http_v2_module --with-http_sub_module --with-http_gzip_static_module --with-http_realip_module --with-http_stub_status_module --with-http_image_filter_module=dynamic --with-pcre-jit

Ubuntu 24.04 and 22.04 showed the same NGINX version and configure flags, but nginx -V reported OpenSSL 3.0.x from those releases.

Test the Local Default Page

Request the local HTTP endpoint to confirm the service is listening and returning the default page.

curl -I -s http://127.0.0.1

Relevant output includes:

HTTP/1.1 200 OK
Server: nginx/1.29.8
Content-Type: text/html

If you need access from another machine, open the relevant HTTP or HTTPS port in your firewall after confirming the local listener works. The UFW firewall guide for Ubuntu covers rule creation and verification.

HTTP/3 and QUIC Notes

Add --with-http_v3_module to the configure command when you want HTTP/3 support. NGINX documents HTTP/3 support from 1.25.0 onward and recommends OpenSSL 3.5.1 or newer for QUIC builds; see the official NGINX QUIC documentation for current requirements.

When --with-http_v3_module is present, Ubuntu 26.04 currently reports checking for OpenSSL QUIC API ... found during configuration.

Ubuntu 24.04 and 22.04 can configure the module with their OpenSSL 3.0.x packages, but the configure output falls back through checking for OpenSSL QUIC API ... not found and ends with checking for OpenSSL QUIC compatibility ... found.

The compatibility layer can build HTTP/3 but does not support early data. If full QUIC feature support is required on Ubuntu 24.04 or 22.04, build against OpenSSL 3.5.1 or newer with --with-openssl=/path, or use a QUIC-capable TLS stack listed in the NGINX documentation.

HTTP/3 uses QUIC over UDP. If you serve HTTP/3 on port 443, your firewall, cloud security group, CDN tunnel, reverse proxy, or load balancer must allow UDP 443 as well as TCP 443.

Upgrade Source-Built NGINX

Create an NGINX Source Update Script

The script below creates a reusable update-nginx-source command in ~/.local/bin. It checks your installed version, resolves the current nginx.org branch tarball, verifies the signature, rebuilds with your existing configure flags, tests the configuration, and reloads the service only after the build succeeds.

mkdir -p "$HOME/.local/bin"

cat <<'EOF' > "$HOME/.local/bin/update-nginx-source"
#!/usr/bin/env bash
set -euo pipefail

BUILD_DIR="$HOME/src/nginx"
NGINX_BIN="/usr/sbin/nginx"
DOWNLOAD_PAGE="https://nginx.org/en/download.html"
CHANNEL="${NGINX_CHANNEL:-mainline}"

if [ "${EUID}" -eq 0 ]; then
  echo "Run this script as a regular user. It calls sudo only for install and reload steps."
  exit 1
fi

for cmd in curl gpg grep head make sed tar; do
  if ! command -v "$cmd" >/dev/null 2>&1; then
    echo "Error: $cmd is required but not installed."
    echo "Run: sudo apt install build-essential curl gnupg"
    exit 1
  fi
done

if [ ! -x "$NGINX_BIN" ]; then
  echo "Error: NGINX binary not found at $NGINX_BIN."
  echo "Update NGINX_BIN in this script if your build uses another path."
  exit 1
fi

case "$CHANNEL" in
  mainline) SECTION="Mainline version" ;;
  stable) SECTION="Stable version" ;;
  *)
    echo "Error: set NGINX_CHANNEL to mainline or stable."
    exit 1
    ;;
esac

CURRENT_VERSION="$("$NGINX_BIN" -v 2>&1 | sed -n 's/^nginx version: nginx\///p')"
LATEST_TARBALL="$(curl -fsSL "$DOWNLOAD_PAGE" | tr '<' '\n' | sed -n "/h4>${SECTION}/,/\/table>/p" | grep -Eo 'nginx-[0-9]+\.[0-9]+\.[0-9]+\.tar\.gz' | head -n1)"

if [ -z "$LATEST_TARBALL" ]; then
  echo "Error: could not detect the latest $CHANNEL NGINX tarball."
  exit 1
fi

LATEST_VERSION="${LATEST_TARBALL#nginx-}"
LATEST_VERSION="${LATEST_VERSION%.tar.gz}"

echo "Channel:         $CHANNEL"
echo "Current version: $CURRENT_VERSION"
echo "Latest version:  $LATEST_VERSION"

if [ "$CURRENT_VERSION" = "$LATEST_VERSION" ]; then
  echo "Already up to date."
  exit 0
fi

CONFIGURE_ARGS="$("$NGINX_BIN" -V 2>&1 | sed -n 's/^configure arguments: //p')"

if [ -z "$CONFIGURE_ARGS" ]; then
  echo "Error: could not read configure arguments from nginx -V."
  exit 1
fi

mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR"

rm -rf "nginx-${LATEST_VERSION}" "$LATEST_TARBALL" "$LATEST_TARBALL.asc"
curl -fLO "https://nginx.org/download/$LATEST_TARBALL"
curl -fLO "https://nginx.org/download/$LATEST_TARBALL.asc"

KEY_DIR="$BUILD_DIR/nginx-release-keys"
GNUPGHOME="$BUILD_DIR/gnupg"
mkdir -p "$KEY_DIR" "$GNUPGHOME"
chmod 700 "$GNUPGHOME"
export GNUPGHOME

for key in arut pluknet sb thresh; do
  curl -fsSLo "$KEY_DIR/${key}.key" "https://nginx.org/keys/${key}.key"
done

gpg --import "$KEY_DIR"/*.key >/dev/null
gpg --verify "$LATEST_TARBALL.asc" "$LATEST_TARBALL"

tar -xzf "$LATEST_TARBALL"
cd "nginx-${LATEST_VERSION}"

# This uses the simple configure flags shown in this article. If your saved
# configure arguments contain quoted custom compiler or linker flags, set
# CONFIGURE_ARGS manually before running ./configure.
# shellcheck disable=SC2086
./configure $CONFIGURE_ARGS
make -j"$(nproc)"
sudo make install
sudo nginx -t
sudo systemctl reload nginx || sudo nginx -s reload

echo "Updated to $("$NGINX_BIN" -v 2>&1 | sed -n 's/^nginx version: nginx\///p')"
EOF

Make the helper executable, then run it with its full path. By default it tracks mainline; use NGINX_CHANNEL=stable only if your source build intentionally follows the stable branch. After a new login, Ubuntu usually includes ~/.local/bin in PATH, so update-nginx-source may also work directly.

chmod +x "$HOME/.local/bin/update-nginx-source"
"$HOME/.local/bin/update-nginx-source"

When the installed version already matches the selected branch, the no-op path returns:

Channel:         mainline
Current version: 1.29.8
Latest version:  1.29.8
Already up to date.

Troubleshoot NGINX Source Build Issues

Missing PCRE2, OpenSSL, zlib, or GD Libraries

If ./configure stops with a missing-library error such as the HTTP rewrite module requires the PCRE library, reinstall the dependency package and rerun the configure command.

sudo apt install libpcre2-dev libssl-dev zlib1g-dev libgd-dev

For OpenSSL-specific failures, confirm the development package and runtime version.

openssl version
dpkg -l libssl-dev | grep '^ii'

Dynamic Module Is Not Binary Compatible

Dynamic modules must match the NGINX version and build options they load into. If sudo nginx -t reports that a module is not binary compatible, do not force-load the old .so file.

Rebuild the module from the same source version and reuse the configure arguments shown by nginx -V. This matters for third-party modules as much as bundled dynamic modules.

Configuration Test Failures

Run the syntax test and fix the file and line that NGINX reports before restarting or reloading the service.

sudo nginx -t

Port 80 or 443 Is Already in Use

If NGINX cannot bind to a web port, check which process already owns the listener.

sudo ss -tlnp '( sport = :80 or sport = :443 )'

Stop the conflicting service or change the NGINX listen port. For a full port-change workflow, use the NGINX port change guide.

Remove Source-Built NGINX

NGINX source releases do not provide a make uninstall target, so removal is a manual cleanup of the files this article created.

Stop and Remove the NGINX Service

Stop the service, disable it at boot, remove the unit file, and reload systemd.

sudo systemctl stop nginx
sudo systemctl disable nginx
sudo rm -f /etc/systemd/system/nginx.service
sudo systemctl daemon-reload

Remove the Binary and Modules

Remove the installed binary and dynamic module directory.

sudo rm -f /usr/sbin/nginx
sudo rm -rf /usr/lib/nginx
hash -r
command -v nginx || echo "nginx removed from PATH"
nginx removed from PATH

Remove Configuration, Logs, and Build Files

The next commands permanently delete source-built NGINX configuration, default web files, logs, cache directories, downloaded source trees, and the update helper. Back up site configs and logs before running them.

sudo rm -rf /etc/nginx /var/log/nginx /var/cache/nginx
rm -rf "$HOME/src/nginx"
rm -f "$HOME/.local/bin/update-nginx-source"

Remove Build Dependencies When Safe

If you installed the development packages only for this source build and do not need them for anything else, remove the direct build dependencies first.

sudo apt remove build-essential libpcre2-dev libssl-dev zlib1g-dev libgd-dev libxml2-dev libxslt1-dev

Review the automatic cleanup list before executing it. Skip the final command if the dry run includes kernels, desktop packages, database clients, or anything you recognize as unrelated to this build.

sudo apt autoremove --dry-run

If the dry run only lists packages that were pulled in for the source build, run the cleanup.

sudo apt autoremove

Next Steps

Source-built NGINX is now running on Ubuntu with your selected module set, verified source files, and systemd management. From here, configure your server blocks carefully for the layout you created, then secure public sites with Let’s Encrypt on NGINX for Ubuntu or tune static delivery with the NGINX gzip compression guide.

Follow LinuxCapable

Want more LinuxCapable guides in Google?

Add LinuxCapable as a preferred source so Google can show more of our fresh Linux tutorials in Top Stories and From your sources when relevant.

Add LinuxCapable as a preferred source on Google
Search LinuxCapable

Need another guide?

Search LinuxCapable for package installs, commands, troubleshooting, and follow-up guides related to what you just read.

Found this guide useful?

Support LinuxCapable to keep tutorials free and up to date.

Buy me a coffeeBuy me a coffee
Before commenting, please review our Comments Policy.
Formatting tips for your comment

You can use basic HTML to format your comment. Useful tags currently allowed in published comments:

You type Result
<code>command</code> command
<strong>bold</strong> bold
<em>italic</em> italic
<blockquote>quote</blockquote> quote block

Got a Question or Feedback?

We read and reply to every comment - let us know how we can help or improve this guide.

Let us know you are human: