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.
| Category | Option | Use |
|---|---|---|
| Paths | --prefix=/path | Sets the base path used by relative defaults. |
--sbin-path=/path | Sets the installed NGINX binary path. | |
--conf-path=/path | Sets the main nginx.conf path. | |
--modules-path=/path | Sets the directory for dynamic modules. | |
| HTTP and TLS | --with-http_ssl_module | Enables HTTPS support. |
--with-http_v2_module | Enables HTTP/2 support. | |
--with-http_v3_module | Enables HTTP/3 support when the TLS stack supports QUIC. | |
--with-http_sub_module | Enables the sub_filter response-substitution directives. | |
--with-http_gzip_static_module | Serves pre-compressed .gz assets. | |
--with-http_realip_module | Restores client IPs behind proxies and CDNs. | |
--with-http_stub_status_module | Adds the lightweight status module. | |
| Dynamic modules | --with-http_image_filter_module=dynamic | Builds the image filter module as a loadable .so file. |
--with-http_xslt_module=dynamic | Builds the XSLT module as a loadable module when the optional dependencies are installed. | |
--add-dynamic-module=/path | Builds a third-party module as a dynamic module. | |
| Performance | --with-threads | Enables thread pools for blocking disk I/O. |
--with-file-aio | Enables asynchronous file I/O support. | |
--with-pcre-jit | Enables 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-enabledlayout or aconf.dinclude. Before following a separate NGINX configuration guide, inspect/etc/nginx/nginx.confand 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.


Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed in published comments:
<code>command</code>command<strong>bold</strong><em>italic</em><blockquote>quote</blockquote>