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

Building NGINX from source on Ubuntu lets you control modules, compile flags, and install paths so the web server matches your workload. It is a solid fit when you need third-party modules, want early access to new protocol features, or must compile against a specific OpenSSL build.

This guide covers build dependencies, downloading the source, configuring and compiling NGINX, creating a systemd service, and verifying the final installation.

Source builds do not receive automatic security updates from Ubuntu. Plan to monitor NGINX security advisories and rebuild when new releases or patches are published.

These steps target Ubuntu 26.04 LTS, 24.04 LTS, and 22.04 LTS. The build workflow is the same across these releases, but HTTP/3 support depends on your OpenSSL version: Ubuntu 26.04 ships OpenSSL 3.5.3, while 24.04 and 22.04 ship OpenSSL 3.0.x and use the compatibility layer for QUIC.

If you prefer repository-managed updates, follow our NGINX installation guide for Ubuntu instead.

Install Build Dependencies

Update Package Lists

Refresh your package index so you install the latest build dependencies:

sudo apt update

Install Required Packages

Install the core build tools and libraries NGINX needs to compile:

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

Here is what each package provides:

  • build-essential: GCC, make, and headers required to compile software.
  • curl: Downloads source tarballs without needing a browser.
  • libpcre2-dev: PCRE2 regular expressions used for rewrites and location matching.
  • libssl-dev: TLS support for HTTPS and HTTP/2.
  • zlib1g-dev: Compression support for gzip and related modules.
  • libgd-dev: Required for the image filter module when building it as a dynamic module.

Optional Libraries for XSLT Support

Install these only if you plan to enable the XSLT module:

sudo apt install libxml2-dev libxslt1-dev

The XSLT module is enabled with --with-http_xslt_module or --with-http_xslt_module=dynamic during configuration.

The legacy GeoIP module relies on the deprecated libGeoIP library and is not covered in this guide; if you need GeoIP2, use the third-party module from MaxMind instead.

Download the NGINX Source Code

Create a Build Directory

Keep source builds in a user-owned directory to avoid permission issues:

mkdir -p ~/src/nginx
cd ~/src/nginx

Choose a Release and Download the Tarball

Pick a version from the official NGINX download page. Mainline is the recommended channel for feature updates and fixes, while stable changes less frequently.

Set a version variable, then download the matching tarball:

NGINX_VERSION=1.29.4
curl -fLO --progress-bar https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz

The -fLO flags fail fast on HTTP errors, follow redirects, and write the file with its original name.

#=#=#
##O#-#
##O=#  #
#=#=-#  #
-#O#- #   #
#                                                                          2.5%
#####                                                                      7.5%
###########                                                               16.2%
########################                                                  33.8%
#################################################                         68.8%
######################################################################## 100.0%

Extract and Enter the Source Tree

Extract the archive and move into the source directory:

tar -xzf nginx-${NGINX_VERSION}.tar.gz
cd nginx-${NGINX_VERSION}

Configure the Build

The ./configure script detects system libraries and generates the Makefile based on the modules and paths you choose.

CategoryOptionWhat It Does
Paths--prefix=/pathSets the base install prefix for default paths.
--sbin-path=/pathSets the NGINX binary location.
--conf-path=/pathSets the main nginx.conf path.
--modules-path=/pathSets the directory for dynamic modules.
Libraries--with-openssl=/pathBuilds NGINX against OpenSSL sources in that directory.
Core HTTP--with-http_ssl_moduleAdds TLS support.
--with-http_v2_moduleEnables HTTP/2 support.
--with-http_v3_moduleEnables HTTP/3 support with a QUIC-capable TLS stack.
--with-http_gzip_static_moduleServes pre-compressed .gz assets.
--with-http_realip_moduleRestores client IPs behind proxies and CDNs.
--with-http_stub_status_moduleAdds a basic status endpoint.
Dynamic Modules--with-http_image_filter_module=dynamicBuilds the image filter module as a loadable .so.
--with-http_xslt_module=dynamicBuilds the XSLT module as a loadable .so.
--add-module=/pathCompiles a third-party module statically.
--add-dynamic-module=/pathCompiles 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.
--with-pcre-jitEnables PCRE2 JIT for faster regex matching.
--with-pcre=/pathBuilds PCRE2 from source at the given path.
Debugging--with-debugBuilds with debug logging enabled.

For the full option list, run ./configure --help in the source directory.

Configure NGINX for Ubuntu Paths

This example keeps configuration in /etc/nginx, logs in /var/log/nginx, caches in /var/cache/nginx, and installs the binary in /usr/sbin. It also builds the image filter module as a dynamic module so you can load it only when needed.

./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_gzip_static_module \
  --with-http_realip_module \
  --with-http_stub_status_module \
  --with-http_image_filter_module=dynamic \
  --with-pcre-jit

If you do not need the image filter module, remove --with-http_image_filter_module=dynamic and omit libgd-dev from the dependency list.

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

Load the Image Filter Module

If you enabled the image filter module, add a load_module line at the top of /etc/nginx/nginx.conf before the events block:

load_module /usr/lib/nginx/ngx_http_image_filter_module.so;

events {
    worker_connections 1024;
}

http {
    # Your HTTP configuration
}

Compile and Install NGINX

Compile the source. The -j$(nproc) flag uses all available CPU cores to speed up the build.

make -j$(nproc)
make -f objs/Makefile
cc -c -pipe  -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g  -I src/core -I src/event -I src/event/modules -I src/event/quic -I src/os/unix -I objs \
	-o objs/src/core/nginx.o \
	src/core/nginx.c

Install the compiled binary and configuration files to the paths you set during configuration:

sudo make install

Create Runtime Directories

The temporary cache paths you configured are not created automatically. Create them before starting NGINX:

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

Set ownership so the www-data worker user can write temporary files:

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

Create a systemd Service

Use systemd to manage NGINX like any other Ubuntu service:

Create the service unit:

sudo nano /etc/systemd/system/nginx.service

Use the unit below, adjusting the binary path if you installed NGINX elsewhere:

[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

Reload systemd and start the service:

sudo systemctl daemon-reload
sudo systemctl start nginx

Enable NGINX at boot so it starts automatically after reboots:

sudo systemctl enable nginx

Verify the Installation

Check Configuration Syntax

Validate your configuration before serving traffic:

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

Confirm the Build Flags

Check the compiled version and configure arguments:

nginx -V
nginx version: nginx/1.29.4
built by gcc 15.2.0 (Ubuntu 15.2.0-11ubuntu1) 
built with OpenSSL 3.5.3 16 Sep 2025
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_gzip_static_module --with-http_realip_module --with-http_stub_status_module --with-http_image_filter_module=dynamic --with-pcre-jit

Test the Default Page

Request the default page locally to confirm the service is responding:

curl -I -s http://127.0.0.1
HTTP/1.1 200 OK
Server: nginx/1.29.4
Date: Sat, 17 Jan 2026 01:41:00 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Sat, 17 Jan 2026 01:29:47 GMT
Connection: keep-alive
ETag: "696ae60b-267"
Accept-Ranges: bytes

If you are testing from another machine, ensure port 80 is allowed through your firewall. See our UFW firewall guide for Ubuntu if you need help opening HTTP access.

HTTP/3 and QUIC Notes

Add --with-http_v3_module to your configure command when you want HTTP/3 support. NGINX recommends OpenSSL 3.5.1 or higher for QUIC support; see the official QUIC documentation for current guidance.

On Ubuntu 26.04, the configure check reports the QUIC API as available. On Ubuntu 24.04 and 22.04, you will see the OpenSSL compatibility checks instead:

checking for OpenSSL QUIC API ... not found
checking for BoringSSL-like QUIC API ... not found
checking for OpenSSL QUIC compatibility ... found

The compatibility layer allows HTTP/3 builds but does not support early data. If you need full QUIC feature support, build NGINX with OpenSSL 3.5.1+ using --with-openssl=/path or a QUIC-capable TLS stack such as BoringSSL or QuicTLS.

Upgrade Source-Built NGINX

Create an Update Script

The script below checks your installed version, compares it with the latest mainline release on nginx.org, and rebuilds only when an update is needed.

Create the script file:

cat <<'EOF' > ~/nginx-update.sh
#!/usr/bin/env bash
set -euo pipefail

# Settings
BUILD_DIR="$HOME/src/nginx"
NGINX_BIN="/usr/sbin/nginx"
DOWNLOAD_PAGE="https://nginx.org/en/download.html"

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

for cmd in curl tar make gcc; 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"
    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 to match your install."
  exit 1
fi

# Version detection
CURRENT_VERSION="$($NGINX_BIN -v 2>&1 | sed -n "s/^nginx version: nginx\\///p")"
LATEST_VERSION="$(curl -fsSL "$DOWNLOAD_PAGE" | grep -oE "nginx-[0-9]+\\.[0-9]+\\.[0-9]+" | head -n 1 | cut -d- -f2)"

if [ -z "$LATEST_VERSION" ]; then
  echo "Error: Could not detect the latest NGINX version."
  exit 1
fi

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

# Download and build
mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR"

rm -rf "nginx-${LATEST_VERSION}" "nginx-${LATEST_VERSION}.tar.gz"

curl -fLO --progress-bar "https://nginx.org/download/nginx-${LATEST_VERSION}.tar.gz"
tar -xzf "nginx-${LATEST_VERSION}.tar.gz"
cd "nginx-${LATEST_VERSION}"

./configure $CONFIGURE_ARGS
make -j"$(nproc)"
sudo make install

# Verify and reload
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 script executable, then run it:

chmod +x ~/nginx-update.sh
~/nginx-update.sh
Current version: 1.29.4
Latest version:  1.29.4
Already up to date.

If your saved configure arguments include quoted values (for example, custom --with-cc-opt or --with-ld-opt flags), set CONFIGURE_ARGS manually in the script. The script tracks the latest mainline release; for the stable channel, replace the LATEST_VERSION line with the stable version from nginx.org.

Troubleshooting Common Issues

Missing Development Libraries

If ./configure reports missing libraries, install the required packages and rerun the configure step:

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

OpenSSL and HTTP/3 Mismatches

Verify your build flags with nginx -V and confirm you added --with-http_v3_module if HTTP/3 is required. For full QUIC support, build against OpenSSL 3.5.1+ or another QUIC-capable TLS stack and consult the NGINX QUIC documentation for the current requirements.

Configuration Test Failures

If NGINX fails to start due to config errors, rerun sudo nginx -t and fix the line number reported in the output, then retest.

Port Already in Use

If NGINX cannot bind to port 80 or 443, identify the process already using the port:

sudo ss -tlnp | grep :80

Stop the conflicting service or change the NGINX listen port. See our port change guide for NGINX for a step-by-step walkthrough.

Remove Source-Built NGINX

Stop and disable the service:

sudo systemctl stop nginx
sudo systemctl disable nginx

Remove the systemd unit and reload systemd:

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

Remove the NGINX binary and dynamic module directory:

sudo rm -f /usr/sbin/nginx
sudo rm -rf /usr/lib/nginx

Removing configuration and logs deletes all site configs and history. Back up anything you want to keep before running the next commands.

sudo rm -rf /etc/nginx
sudo rm -rf /var/log/nginx /var/cache/nginx

Remove the build directory from your home folder:

rm -rf ~/src/nginx

If you installed build dependencies only for this build and do not need them elsewhere, you can remove them:

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

Next Steps

Customize /etc/nginx/nginx.conf for your sites, then consider enabling gzip compression using our NGINX gzip guide.

Leave a Comment

Let us know you are human: