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_moduleflag 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
nginxservice from APT, remove that package before you enable the source-built unit. The packaged service and the source-built service use the samenginx.servicename.
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/htmlseparately 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.


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
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 mechanismsSO_SETFIB,SO_BINDANY,IP_BINDANY: FreeBSD/OpenBSD routing featuressetproctitle(),struct dirent.d_namlen: BSD library functionssys/filio.h: BSD header not present on Linuxcrypt(): Checked for HTTP Basic authentication (not needed unless usingauth_basic)Linux uses
epollinstead 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.