How to Build NGINX from Source on Debian 13, 12 and 11

Build NGINX from source on Debian 13, 12 and 11 with HTTP/2, HTTP/3 and SSL. Includes systemd setup and verification.

Last updatedAuthorJoshua JamesRead time8 minGuide typeDebian

HTTP/3 support, upstream release control, and third-party modules are the usual reasons to build NGINX from source on Debian instead of taking the default package. When you build NGINX from source on Debian, you decide which modules are compiled in, keep the upstream stable branch within reach, and avoid waiting on Debian’s package cadence when you need newer web-server features.

The build path here pulls the current stable tarball from nginx.org, verifies its detached PGP signature, and compiles it on Debian 13 (trixie), Debian 12 (bookworm), and Debian 11 (bullseye). It replaces the upstream sample configuration with a Debian-friendly conf.d layout, adds a systemd unit, and leaves you with a reusable update-nginx-source helper for later upgrades. If you would rather stay with packaged builds, use install NGINX on Debian for the default APT package or install NGINX mainline on Debian for the upstream repository track.

The source build and the --with-http_v3_module flag work on Debian 13, Debian 12, and Debian 11. Debian 13 is still the best fit if you also want OpenSSL 3.5.x features such as HTTP/3 0-RTT support alongside the same stable NGINX branch.

Build NGINX from Source on Debian

Debian desktop installs may already include some compiler tools, but server, cloud, and minimal images usually do not. Start by refreshing package metadata so the compiler, verification tools, and development headers come from the current Debian repositories.

Update Debian Before Building NGINX from Source

Refresh APT and apply any pending upgrades before you install the compiler toolchain.

sudo apt update && sudo apt upgrade -y

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

Install NGINX Source Build Dependencies on Debian

Install the compiler toolchain, PCRE2 headers, OpenSSL headers, compression libraries, image-filter dependencies, download client, certificate bundle, and GnuPG tools used by the source verification step.

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

Debian 13, Debian 12, and Debian 11 all provide libpcre2-dev, so this workflow can use one dependency set on all three releases. If you prefer to build against older PCRE on Debian 12 or 11, change the package to libpcre3-dev and expect the configure summary to say using system PCRE library instead of using system PCRE2 library.

Download the Current Stable NGINX Source Tarball on Debian

Keep using the same terminal session for the next steps. The first command resolves the current stable tarball name from the nginx.org download page so the workflow follows the stable release rather than a hardcoded version.

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

NGINX_TARBALL="$(curl -fsSL https://nginx.org/en/download.html | tr '<' '\n' | sed -n '/h4>Stable version/,/\/table>/p' | grep -Eo 'nginx-[0-9]+\.[0-9]+\.[0-9]+\.tar\.gz' | head -n1)"
if [ -n "$NGINX_TARBALL" ]; then
  printf '%s\n' "$NGINX_TARBALL"
else
  printf 'Could not detect the current stable tarball.\n' >&2
  exit 1
fi
nginx-1.30.1.tar.gz

The exact tarball name changes when nginx.org publishes a new stable point release. The sample output uses nginx-1.30.1, but the resolver is the source of truth when a newer stable tarball appears.

Download the NGINX Tarball and Signature on Debian

Download both the source tarball and its detached signature file from the version stored in $NGINX_TARBALL.

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

Verify the NGINX Source Signature on Debian

Import the current NGINX release-signing keys into a temporary GnuPG keyring, then verify the detached signature before extracting the source. Compare the fingerprint in the output with the NGINX PGP public keys page.

GNUPGHOME="$(mktemp -d)"
export GNUPGHOME
trap 'rm -rf "$GNUPGHOME"' EXIT

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

gpg --import arut.key pluknet.key sb.key thresh.key
if gpg --verify "$NGINX_TARBALL.asc" "$NGINX_TARBALL"; then
  rm -rf "$GNUPGHOME"
  unset GNUPGHOME
  trap - EXIT
else
  printf 'Signature verification failed. Stop before extracting the tarball.\n' >&2
  rm -rf "$GNUPGHOME"
  unset GNUPGHOME
  trap - EXIT
  exit 1
fi

Relevant output includes:

gpg: Good signature from "Sergey Kandaurov <s.kandaurov@f5.com>" [unknown]
Primary key fingerprint: D678 6CE3 03D9 A902 2998  DC6C C846 4D54 9AF7 5C0A

GnuPG may also print a trust warning because these keys are not certified in your personal keyring. Treat the missing Good signature line as the stop condition.

Extract the Verified NGINX Source Tree on Debian

Extract the verified tarball and enter the matching source directory.

tar -xzf "$NGINX_TARBALL"
cd "${NGINX_TARBALL%.tar.gz}"
test -x configure && printf 'source tree ready: %s\n' "$PWD"
source tree ready: /home/your-user/nginx-source/nginx-1.30.1

Configure the NGINX Source Build on Debian

These configure flags install the binary under /usr/sbin, place the main configuration file at /etc/nginx/nginx.conf, write dynamic modules under /etc/nginx/modules, and keep the PID file in the normal runtime directory at /run/nginx.pid.

./configure \
  --prefix=/var/www/html \
  --sbin-path=/usr/sbin/nginx \
  --conf-path=/etc/nginx/nginx.conf \
  --http-log-path=/var/log/nginx/access.log \
  --error-log-path=/var/log/nginx/error.log \
  --with-pcre \
  --lock-path=/var/lock/nginx.lock \
  --pid-path=/run/nginx.pid \
  --with-http_ssl_module \
  --with-http_image_filter_module=dynamic \
  --modules-path=/etc/nginx/modules \
  --with-http_v2_module \
  --with-http_v3_module \
  --with-stream=dynamic \
  --with-http_addition_module \
  --with-http_mp4_module

Relevant output includes:

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

  nginx path prefix: "/var/www/html"
  nginx binary file: "/usr/sbin/nginx"
  nginx modules path: "/etc/nginx/modules"
  nginx configuration prefix: "/etc/nginx"
  nginx configuration file: "/etc/nginx/nginx.conf"
  nginx pid file: "/run/nginx.pid"
  nginx error log file: "/var/log/nginx/error.log"
  nginx http access log file: "/var/log/nginx/access.log"
  nginx http client request body temporary files: "client_body_temp"

With this dependency set, Debian 13, Debian 12, and Debian 11 report using system PCRE2 library. The OpenSSL version differs by release, but the configure summary should still show the system OpenSSL and zlib libraries.

Compile and Install NGINX on Debian

The -j"$(nproc)" form tells make to use the available CPU cores instead of compiling serially. After the build finishes, sudo make install writes the binary, modules, and default support files into the paths from the configure step.

make -j"$(nproc)"
sudo make install

Verify the installed binary before you start replacing the default upstream configuration.

/usr/sbin/nginx -v
nginx version: nginx/1.30.1

If a regular Debian SSH shell ever reports nginx: command not found, the binary is still installed. The common issue is that /usr/sbin is outside the unprivileged PATH, which is why the version checks here use /usr/sbin/nginx.

Check Compiled NGINX Modules on Debian

Use /usr/sbin/nginx -V when you need to confirm which configure flags were compiled into the installed binary. The command writes its details to standard error, so redirect it before filtering the flags used in this build.

/usr/sbin/nginx -V 2>&1 | tr ' ' '\n' | grep -E '^--with-(http_ssl_module|http_image_filter_module|http_v3_module|stream)'
--with-http_ssl_module
--with-http_image_filter_module=dynamic
--with-http_v3_module
--with-stream=dynamic

Dynamic modules from this layout are installed under /etc/nginx/modules/. Add a load_module directive only for a dynamic module you actually plan to use.

Write a Debian-Friendly NGINX Configuration

The source build installs the upstream sample nginx.conf, which does not create Debian’s sites-available/sites-enabled layout or a conf.d include by default. Replace that sample with a tested Debian-friendly layout, keep temporary files under /var/cache/nginx instead of the web root, and create a simple default server block under /etc/nginx/conf.d/.

The quoted EOF markers matter here. They keep NGINX variables such as $uri literal while sudo tee writes the root-owned files.

sudo install -d -m 0755 /etc/nginx/conf.d /var/cache/nginx /var/log/nginx /var/www/html

sudo tee /etc/nginx/nginx.conf > /dev/null <<'EOF'
user www-data;
worker_processes auto;

error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    sendfile on;
    tcp_nopush on;
    keepalive_timeout 65;
    server_tokens off;

    client_body_temp_path /var/cache/nginx/client_temp;
    proxy_temp_path /var/cache/nginx/proxy_temp;
    fastcgi_temp_path /var/cache/nginx/fastcgi_temp;
    uwsgi_temp_path /var/cache/nginx/uwsgi_temp;
    scgi_temp_path /var/cache/nginx/scgi_temp;

    include /etc/nginx/conf.d/*.conf;
}
EOF
sudo tee /etc/nginx/conf.d/default.conf > /dev/null <<'EOF'
server {
    listen 80;
    listen [::]:80;
    server_name _;
    root /var/www/html;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}
EOF

printf '%s\n' '<!doctype html><html><body><h1>NGINX source build on Debian</h1></body></html>' | sudo tee /var/www/html/index.html > /dev/null

Keep only the server {} block in /etc/nginx/conf.d/default.conf. The main http {} block already lives in /etc/nginx/nginx.conf, and copying a second http block into conf.d triggers the familiar http directive is not allowed here error. Use this default block for the local smoke test; when you are ready to host real domains, move on to configure NGINX server blocks and virtual hosts.

Test the new configuration before you hand it to systemd.

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

Create the NGINX Systemd Service on Debian

Source-built NGINX does not ship a Debian systemd unit, so create one explicitly and reload systemd before you try to enable the service.

printf '%s\n' \
  '[Unit]' \
  'Description=The NGINX HTTP and reverse proxy server' \
  'After=syslog.target 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=/bin/kill -s QUIT $MAINPID' \
  'PrivateTmp=true' \
  '' \
  '[Install]' \
  'WantedBy=multi-user.target' | sudo tee /etc/systemd/system/nginx.service > /dev/null

sudo systemctl daemon-reload

If this Debian host already has the packaged nginx service from APT, remove that package before you enable the source-built unit. The packaged service and the source-built service use the same nginx.service name.

Start and Verify the NGINX Source Build on Debian

Enable the service for future boots, start it now, and then verify both systemd state and a real HTTP response from the local listener.

sudo systemctl enable nginx
sudo systemctl start nginx
systemctl is-active nginx
systemctl is-enabled nginx
active
enabled

The local HTTP check is more useful than a browser-only instruction because it proves the listener is answering before you involve DNS, firewalls, or another machine.

curl -fsSI http://127.0.0.1/ | sed -n '1p;/^Server:/p;/^Content-Type:/p'
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/html

Confirm the response body comes from the default server block you just wrote.

curl -fsS http://127.0.0.1/ | grep -o 'NGINX source build on Debian'
NGINX source build on Debian

From another machine, replace 127.0.0.1 with the server IP or DNS name. If this host will be reachable from the network, configure the firewall before you expose ports 80 and 443 with install UFW on Debian.

Troubleshoot NGINX Source Builds on Debian

These failures are the most likely to interrupt this source-build workflow: a missing systemd unit, a port conflict on 80, normal cross-platform configure checks, and missing development libraries.

Fix Unit nginx.service Could Not Be Found on Debian

This error means systemd has not loaded the source-built service file yet, or the file was removed. Recreate the unit from the install section if needed, then reload systemd and start the service again.

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

Fix Port 80 Already in Use for NGINX on Debian

If NGINX refuses to start because port 80 is already bound, use ss instead of lsof. ss is present on standard Debian installs, so you do not need another package just to inspect the listener.

sudo ss -tlnp | grep ':80'

A matching LISTEN row identifies the process holding port 80. Stop the conflicting service before you start the source-built NGINX service again.

sudo systemctl stop apache2
sudo systemctl start nginx

Understand Not Found Checks in NGINX Configure Output

Some not found lines in ./configure output are normal. NGINX checks for platform-specific features across Linux, BSD, macOS, and Solaris; Linux builds usually skip items such as kqueue, /dev/poll, SO_ACCEPTFILTER, and several BSD-only headers while detecting Linux mechanisms such as epoll.

Treat the final result as the signal. Continue when ./configure reaches the configuration summary. Stop when it exits with an error about a required library, reinstall the dependencies, and rerun ./configure from the extracted source directory.

Fix Missing PCRE or OpenSSL Libraries When Building NGINX on Debian

If ./configure stops on a missing PCRE, OpenSSL, zlib, or image-filter library, reinstall the dependency set used by the source build. This is safe to rerun because APT keeps already installed packages in place.

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

Then rerun ./configure from the extracted NGINX source directory. If you deliberately changed the PCRE package to libpcre3-dev on Debian 12 or Debian 11, keep that choice consistent when troubleshooting.

Update NGINX Source Builds on Debian

Source builds do not receive updates through APT, so it is worth keeping a small helper in /usr/local/bin. The helper checks the stable section of the nginx.org download page, skips work when the installed binary is already current, verifies the tarball signature before rebuilding, and preserves your existing /etc/nginx/nginx.conf because make install does not overwrite that file once it exists.

Run the update helper manually instead of from cron. A source upgrade can fail on dependency changes, compiler errors, or configuration issues, and you want that output in front of you before the running service is replaced.

Create the Update NGINX Source Helper on Debian

Install the helper in /usr/local/bin so you can rerun it from any directory with sudo update-nginx-source.

sudo install -m 0755 /dev/stdin /usr/local/bin/update-nginx-source <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

BUILD_DIR="/usr/local/src/nginx-build"
DOWNLOAD_PAGE="https://nginx.org/en/download.html"
BASE_URL="https://nginx.org/download"

for cmd in curl grep head sed tr; do
  if ! command -v "$cmd" >/dev/null 2>&1; then
    echo "Required command not found: $cmd" >&2
    exit 1
  fi
done

CURRENT_VERSION="$([ -x /usr/sbin/nginx ] && /usr/sbin/nginx -v 2>&1 | sed 's#.*nginx/##' || true)"
TARBALL="$(curl -fsSL "$DOWNLOAD_PAGE" | tr '<' '\n' | sed -n '/h4>Stable version/,/\/table>/p' | grep -Eo 'nginx-[0-9]+\.[0-9]+\.[0-9]+\.tar\.gz' | head -n1 || true)"
TARGET_VERSION="$(printf '%s\n' "$TARBALL" | sed 's/^nginx-//; s/\.tar\.gz$//')"

if [ -z "$TARBALL" ] || [ -z "$TARGET_VERSION" ]; then
  echo "Could not determine the current stable NGINX tarball." >&2
  exit 1
fi

if [ -n "$CURRENT_VERSION" ] && [ "$CURRENT_VERSION" = "$TARGET_VERSION" ]; then
  echo "NGINX is already at the current stable release: $CURRENT_VERSION"
  exit 0
fi

for cmd in gpg install make mktemp nproc systemctl tar; do
  if ! command -v "$cmd" >/dev/null 2>&1; then
    echo "Required command not found: $cmd" >&2
    exit 1
  fi
done

echo "Preparing build directory: $BUILD_DIR"
install -d -m 0755 "$BUILD_DIR"
cd "$BUILD_DIR"
rm -rf "nginx-$TARGET_VERSION" "$TARBALL" "$TARBALL.asc"
rm -f arut.key pluknet.key sb.key thresh.key

echo "Downloading $TARBALL"
curl -fLO "$BASE_URL/$TARBALL"
curl -fLO "$BASE_URL/$TARBALL.asc"

GNUPGHOME="$(mktemp -d)"
export GNUPGHOME
trap 'rm -rf "$GNUPGHOME"' EXIT

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

gpg --import arut.key pluknet.key sb.key thresh.key >/dev/null
gpg --verify "$TARBALL.asc" "$TARBALL"

tar -xzf "$TARBALL"
cd "nginx-$TARGET_VERSION"

echo "Configuring NGINX $TARGET_VERSION"
./configure \
  --prefix=/var/www/html \
  --sbin-path=/usr/sbin/nginx \
  --conf-path=/etc/nginx/nginx.conf \
  --http-log-path=/var/log/nginx/access.log \
  --error-log-path=/var/log/nginx/error.log \
  --with-pcre \
  --lock-path=/var/lock/nginx.lock \
  --pid-path=/run/nginx.pid \
  --with-http_ssl_module \
  --with-http_image_filter_module=dynamic \
  --modules-path=/etc/nginx/modules \
  --with-http_v2_module \
  --with-http_v3_module \
  --with-stream=dynamic \
  --with-http_addition_module \
  --with-http_mp4_module

echo "Compiling NGINX $TARGET_VERSION"
make -j"$(nproc)"

echo "Installing NGINX $TARGET_VERSION"
make install

echo "Testing NGINX configuration"
/usr/sbin/nginx -t

echo "Restarting nginx"
systemctl restart nginx

echo "Update complete"
/usr/sbin/nginx -v
EOF
command -v update-nginx-source
/usr/local/bin/update-nginx-source

Run the Update NGINX Source Helper on Debian

Run the helper as root so it can install the new binary, test the configuration, and restart NGINX after a successful build.

sudo update-nginx-source
NGINX is already at the current stable release: 1.30.1

When nginx.org publishes a newer stable release, the helper downloads the tarball and signature, verifies the signature, recompiles with the same flags, restarts the service, and ends with a fresh nginx version: nginx/x.y.z line.

Remove NGINX Source Builds on Debian

Removing the source build is mostly file cleanup because APT never owned these files. Stop the service first, remove the unit, then remove the binary, helper, runtime directories, and build workspaces that came from the source build.

Stop and Disable NGINX on Debian

Stop the running service before you delete the unit file and the installed binary.

sudo systemctl stop nginx
sudo systemctl disable nginx

Remove the NGINX Source Build Files on Debian

Remove the systemd unit, the source-built binary, the update helper, runtime directories, and reusable build directories.

This cleanup permanently deletes the source-built NGINX configuration, logs, helper script, build cache, and local source workspace. Back up /etc/nginx, site configuration, and any logs you still need before you run it.

Keep $HOME/nginx-source out of the sudo rm command because it belongs to your user account, not root.

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

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

Review /var/www/html separately before you delete anything there. That directory often holds real site content, so it is safer to inspect it manually than to remove it by default in a copy-and-paste cleanup block.

Verify NGINX Source Build Removal on Debian

Use direct file checks instead of which nginx. On Debian, /usr/sbin is often outside an unprivileged PATH, so which can return nothing even while the binary still exists.

test ! -x /usr/sbin/nginx && printf 'nginx binary removed\n'
test ! -e /etc/systemd/system/nginx.service && printf 'nginx systemd unit removed\n'
test ! -e /usr/local/bin/update-nginx-source && printf 'nginx update helper removed\n'
nginx binary removed
nginx systemd unit removed
nginx update helper removed

Conclusion

NGINX built from source is running on Debian with verified source files, a systemd unit, a clean conf.d layout, and a stable-track update helper. The next practical hardening step is secure NGINX with Let’s Encrypt on Debian, then tighten browser-facing policy with configure security headers in NGINX.

Share this guide

Help another Linux user troubleshoot faster

Share this guide with someone troubleshooting Linux systems or saving it for later.

Follow LinuxCapable

Want more LinuxCapable guides in Google?

Add LinuxCapable as a preferred source so Google can show our tutorials more often in Top Stories and mark them as preferred in AI Mode and AI Overviews 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

2 thoughts on “How to Build NGINX from Source on Debian 13, 12 and 11”

  1. How do I make sure these are all “found” during ./configure ?

    checking for sys/filio.h … not found
    checking for nobody group … not found
    checking for /dev/poll … not found
    checking for kqueue … not found
    checking for crypt() … not found
    checking for F_READAHEAD … not found
    checking for F_NOCACHE … not found
    checking for directio() … not found
    checking for SO_SETFIB … not found
    checking for SO_ACCEPTFILTER … not found
    checking for SO_BINDANY … not found
    checking for IP_BINDANY … not found
    checking for IP_RECVDSTADDR … not found
    checking for IP_SENDSRCADDR … not found
    checking for IP_DONTFRAG … not found
    checking for setproctitle() … not found
    checking for struct dirent.d_namlen … not found
    checking for OpenSSL QUIC support … not found

    Reply
    • Thanks for the question, Ben. Good news: those “not found” messages are completely normal and expected on Linux. They do not indicate missing dependencies or a problem with your build.

      The NGINX configure script checks for features across all platforms it supports, including BSD, macOS, Solaris, and Linux. The items you listed are platform-specific features that simply do not exist on Linux:

      • kqueue, SO_ACCEPTFILTER, F_READAHEAD, F_NOCACHE: BSD/macOS kernel features
      • /dev/poll, directio(): Solaris-specific I/O mechanisms
      • SO_SETFIB, SO_BINDANY, IP_BINDANY: FreeBSD/OpenBSD routing features
      • setproctitle(), struct dirent.d_namlen: BSD library functions
      • sys/filio.h: BSD header not present on Linux
      • crypt(): Checked for HTTP Basic authentication (not needed unless using auth_basic)

      Linux uses epoll instead of kqueue for high-performance I/O, and configure will detect that automatically. You cannot “fix” these checks because the underlying features do not exist on your operating system.

      The only potentially actionable item is OpenSSL QUIC support. This requires OpenSSL 3.5.1 or higher for native QUIC, which only Debian 13 (Trixie) currently provides. On Debian 11 and 12, NGINX uses its built-in compatibility layer for basic HTTP/3 functionality. For full 0-RTT support on older Debian versions, you would need to compile with BoringSSL or QuicTLS instead of system OpenSSL.

      As long as configure completes with a “Configuration summary” showing your paths and libraries, the build will succeed.

      Reply
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
<a href="https://example.com">link</a> link
<blockquote>quote</blockquote> quote block

Add to the discussion

Questions, fixes, command output, and version notes help keep this guide current.

Verify before posting: