nginx -v can’t tell you whether a distro build of nginx is patched against CVE-2026-93479 or CVE-2026-15176. On Ubuntu 26.04 the vulnerable package and the fixed one both print nginx/1.28.3, because Canonical backported the fix instead of moving to upstream 1.30.6. The reverse mistake is just as common: a vulnerable version string doesn’t mean a request can reach the bug, since both heap overflows need a specific configuration shape before a crafted request does anything.
So you need two answers: does your package carry the fix, and does your config contain the map regex or alias plus rewrite pattern that F5 describes. This guide checks both, then patches Ubuntu, Debian and Amazon Linux 2023 and covers the config changes that close the hole on servers you cannot patch yet, including RHEL-family hosts that have no vendor fix. Everything below was run on Ubuntu 26.04, Ubuntu 24.04 and Debian 13 in October 2026, with Amazon Linux 2023 and Rocky Linux 10 tested in containers.
Both bugs need a specific config shape
F5 published both advisories on 14 October 2026, with nginx 1.30.6 stable and 1.31.7 mainline as the upstream fixes. Each one is a heap buffer overflow in an nginx worker, and each one only fires when the configuration evaluates variables in a particular order. The result F5 describes is a worker crash and restart, so a denial of service, with code execution possible only where ASLR is disabled or can be bypassed. That makes the config check worth as much as the version check, because it tells you whether an unpatched box is reachable today.
What each advisory needs before a crafted request can reach the overflow, in F5’s own terms (K000162607 and K000162416):
- CVE-2026-93479: a
mapwith regex matching, plus arootor access log format that uses the map’s numbered captures before the map’s output variable - CVE-2026-93479, second path: a non-cacheable variable inside a string expression “under certain conditions”
- CVE-2026-15176: a location with
aliasandrewrite, and asetorreturnthat references$request_filename - CVE-2026-15176, second path:
$request_filenameinside alog_format
Version ranges differ between the two. nginx.org lists CVE-2026-15176 for 0.1.29 through 1.31.6, so every nginx you are likely to run is in range. CVE-2026-93479 is listed only for 1.30.0 to 1.30.5 and 1.31.2 to 1.31.6, yet Ubuntu and Debian still patched their 1.18 to 1.28 packages. Their changelogs say why: the fix had to be adapted to script-handling code they had already backported from a newer nginx, so a distro package can carry the bug even when its version number sits outside the upstream range. Trust your distro’s advisory over the upstream range.
The second path on CVE-2026-93479 is the reason not to stop at the config check. F5 does not spell out which conditions make a non-cacheable variable dangerous, so a config that passes every grep in this guide is less likely to be exposed, not proven safe. Patch first, and treat the config work as the bridge for servers that cannot take a package update this week.
How serious is it? Ratings disagree. nginx.org lists both as medium severity, while F5 scores both at CVSS 3.1 8.1 (High) and CVSS 4.0 9.2 (Critical), and Ubuntu’s tracker repeats the 9.2. Neither CVE is in the CISA Known Exploited Vulnerabilities catalog as of 26 October 2026, and FIRST’s EPSS model puts the chance of exploitation in the next 30 days at about 1.0% for CVE-2026-93479 and 0.8% for CVE-2026-15176. No public exploitation has been reported. That’s a “patch in the normal window” profile, not a weekend emergency, unless your config matches the shapes above and you expose it to the internet.
Fixed nginx versions by distribution
Upstream fixed every affected branch in nginx 1.30.6 (stable) and 1.31.7 (mainline). Distributions backport, so the version you compare against depends on where your nginx came from. Ubuntu shipped USN-8955-1 on 26 October 2026, Debian shipped DSA-6575-1 for trixie, and Amazon Linux 2023 shipped ALAS2023-2026-3263 with a full rebase to 1.30.6:
| Source | Release | Fixed package version | Advisory |
|---|---|---|---|
| nginx.org stable | 1.30.x | 1.30.6 | nginx.org, F5 K000162607 / K000162416 |
| nginx.org mainline | 1.31.x | 1.31.7 | same |
| Ubuntu | 26.10 (stonking) | 1.30.4-7ubuntu1.1 | USN-8955-1 |
| Ubuntu | 26.04 LTS (resolute) | 1.28.3-2ubuntu1.12 | USN-8955-1 |
| Ubuntu | 24.04 LTS (noble) | 1.24.0-2ubuntu7.19 | USN-8955-1 |
| Ubuntu | 22.04 LTS (jammy) | 1.18.0-6ubuntu14.22 | USN-8955-1 |
| Debian | 13 (trixie) | 1.26.3-3+deb13u10 | DSA-6575-1 |
| Debian | forky / sid | 1.30.4-8 and later | unstable upload |
| Debian | 12 (bookworm) | not fixed yet | tracker marks it vulnerable |
| Amazon Linux | 2023 | nginx-1.30.6-1.amzn2023.0.1 | ALAS2023-2026-3263 |
| RHEL, Rocky, AlmaLinux | 8, 9, 10 | no fix yet | Red Hat: “a fix is being pursued” |
The same release fixed three more nginx bugs, and the distro advisories above bundle them. They matter for the mitigation section later, because one of them is what the “disable AIO or SSI” advice is about:
| CVE | What it is | Upstream range | Ubuntu releases in USN-8955-1 |
|---|---|---|---|
| CVE-2026-97205 | Use-after-free when AIO is on and an SSI page uses include virtual | 0.8.11 to 1.31.6 | 22.04, 24.04, 26.04, 26.10 |
| CVE-2026-95101 | HTTP/3 request body injection that bypasses client_max_body_size | 1.25.0 to 1.31.6 | 26.04 and 26.10 only |
| CVE-2026-97629 | Session forgery with TLS session tickets (a rotated ticket key left uninitialized) | 1.23.2 to 1.31.6 | 24.04, 26.04, 26.10 |
Package revisions show the patch
Ask your package manager, not the binary. nginx -v reports the upstream base version, which Ubuntu and Debian keep fixed for the life of a release, so the string you see before and after the security update is identical. The Debian revision (ubuntu1.12, +deb13u10) is the part that moves, and the package changelog is where the CVE IDs land. On Debian, nginx lives in /usr/sbin, which is not on a normal user’s PATH, so run it through sudo.
These checks work the same on Ubuntu and Debian, and none of them change anything on the server:
sudo nginx -v
dpkg-query -W -f='${Package} ${Version}\n' 'nginx*' 'libnginx-mod*' 2>/dev/null
apt list --upgradable 2>/dev/null | grep -i nginx
zgrep -c -E 'CVE-2026-(93479|15176)' /usr/share/doc/nginx-common/changelog.Debian.gz
This is the Ubuntu 26.04 lab server before the update. The binary says 1.28.3, the package is the original 1.28.3-2ubuntu1 from the release pocket, apt offers 1.28.3-2ubuntu1.12 from resolute-security, and the changelog mentions neither CVE:
nginx version: nginx/1.28.3 (Ubuntu)
nginx 1.28.3-2ubuntu1
nginx-abi-1.28.3-1
nginx-common 1.28.3-2ubuntu1
nginx-core
nginx-doc
nginx-extras
nginx-light
nginx-common/resolute-updates,resolute-security 1.28.3-2ubuntu1.12 all [upgradable from: 1.28.3-2ubuntu1]
nginx/resolute-updates,resolute-security 1.28.3-2ubuntu1.12 amd64 [upgradable from: 1.28.3-2ubuntu1]
0
Read it from the bottom up. You are exposed on the package side when any of these is true:
- the changelog count is
0 apt list --upgradableprints any nginx line- the installed revision is lower than the fixed one in the table above
The empty entries such as nginx-core and nginx-light are virtual or not-installed names that the wildcard matched, so you can ignore them. Ubuntu 24.04 and Debian 13 looked the same in the lab, with 1.24.0-2ubuntu7 and 1.26.3-3+deb13u7 installed and the fixed revisions waiting.
Ubuntu also ships a CVE-aware helper. pro fix needs no Ubuntu Pro subscription for packages in main, and --dry-run stops it from touching the system:
pro fix CVE-2026-93479 --dry-run
It names the affected source package and prints the exact apt command it would run:
WARNING: The option --dry-run is being used.
No packages will be installed when running this command.
CVE-2026-93479: nginx vulnerabilities
- https://ubuntu.com/security/CVE-2026-93479
1 affected source package is installed: nginx
(1/1) nginx:
A fix is available in Ubuntu standard updates.
{ apt update && apt install --only-upgrade -y nginx nginx-common }
✔ CVE-2026-93479 is resolved.
Don’t read the last line as a result. In dry-run mode pro fix reports what the system would look like after the command, and nothing was installed at that point. Amazon Linux and the RHEL family use rpm -q nginx instead, covered in their own sections below.
nginx -T shows whether your config matches
Why not just grep /etc/nginx? Because you would miss files included from elsewhere and catch disabled sites that never load. Search the configuration nginx actually parsed instead. nginx -T prints every loaded file under a # configuration file header, and a short awk filter carries that header onto each match so you can see where a hit lives.
The patterns use [[:space:]] instead of \s for a reason. Debian’s default awk is mawk, which silently matches nothing with \s, so a clean result there would be a false all-clear. The same commands worked unchanged on Ubuntu’s gawk.
Run the four checks in order. Each one maps to one condition from the advisories:
alias,rewriteand$request_filename(CVE-2026-15176)- regex lines inside
mapblocks (CVE-2026-93479) rootor log lines that use numbered captures like$1(CVE-2026-93479)aio,ssi, the TLS session cache and ticket key settings, and QUIC listeners (the three companion CVEs)
sudo nginx -T 2>/dev/null | awk '/^# configuration file /{f=$4} /^[[:space:]]*(alias|rewrite)[[:space:]]|\$request_filename/{print f, $0}'
sudo nginx -T 2>/dev/null | awk '/^# configuration file /{f=$4} /^[[:space:]]*map[[:space:]]/{m=1} m && /^[[:space:]]*~/{print f, $0} m && /^[[:space:]]*}/{m=0}'
sudo nginx -T 2>/dev/null | awk '/^# configuration file /{f=$4} /^[[:space:]]*(root|log_format|access_log)[[:space:]].*\$[1-9]/{print f, $0}'
sudo nginx -T 2>/dev/null | awk '/^# configuration file /{f=$4} /^[[:space:]]*(aio|ssi|ssl_session_cache|ssl_session_ticket_key)[[:space:]]|listen.*quic/{print f, $0}'
The lab server carried two test files built to match the advisories, plus a shared TLS session cache line added for the fourth check. This is what the four checks printed on Debian 13, and Ubuntu 26.04 printed the same lines:
/etc/nginx/conf.d/cve-lab.conf: alias /srv/files/;
/etc/nginx/conf.d/cve-lab.conf: rewrite ^/files/old/(.*)$ /files/$1 break;
/etc/nginx/conf.d/cve-lab2.conf: log_format fileaudit '$remote_addr $status "$request_filename"';
/etc/nginx/conf.d/cve-lab2.conf: alias /srv/files/;
/etc/nginx/conf.d/cve-lab2.conf: rewrite ^/dl/v1/(.*)$ /dl/$1 break;
/etc/nginx/conf.d/cve-lab2.conf: set $served_file $request_filename;
/etc/nginx/conf.d/cve-lab.conf: ~^/assets/(?<asset_name>.+)$ $asset_name;
/etc/nginx/conf.d/cve-lab2.conf: ~^/thumbs/(.+)\.jpg$ $1;
/etc/nginx/conf.d/cve-lab2.conf: root /srv/thumbs/$1;
/etc/nginx/conf.d/cve-lab.conf: aio off;
/etc/nginx/conf.d/cve-lab.conf: ssl_session_cache shared:SSL:10m;
How do you read that? cve-lab2.conf is the file to worry about. Its /dl/ location has alias, rewrite and a set that reads $request_filename, which is the CVE-2026-15176 shape exactly. Its log_format uses the same variable. It also has a map regex with an unnamed group and a root that uses $1 without ever touching the map’s output variable, which is the CVE-2026-93479 shape. cve-lab.conf shows the opposite cases: an alias with rewrite but no $request_filename, a map that already uses a named capture, and AIO switched off. Its ssl_session_cache shared line is the CVE-2026-97629 shape whenever no ssl_session_ticket_key line appears next to it, as here.
Treat what you find as a review list, not a verdict. alias on its own is everywhere and harmless here, and a map regex only matters when the captures leak out of the map. The danger is the combination inside one location or one variable chain. If all four commands print nothing, your config does not contain the documented triggers, which buys you time but not immunity, because of the “non-cacheable variable” path F5 leaves undefined.
apt patches Ubuntu and Debian in place
On Ubuntu 26.04, 24.04 and 22.04 and on Debian 13, the fix comes through the normal security pocket. A targeted upgrade of the nginx packages is all you need. The lab covered 26.04, 24.04 and Debian 13; 22.04 uses the same command with the revision from the table. --only-upgrade keeps apt from installing nginx on a box where it isn’t already present, which matters if you push the same command to a whole fleet. If the dpkg-query step listed any libnginx-mod-* packages, add them to the same command so every package built from the nginx source moves together:
sudo apt-get update && sudo apt-get install --only-upgrade -y nginx nginx-common
On the Debian 13 lab server, apt pulled both packages from trixie-security, and the last line is the one to look for:
The following packages will be upgraded:
nginx nginx-common
2 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Need to get 725 kB of archives.
After this operation, 6144 B of additional disk space will be used.
Get:2 https://deb.debian.org/debian-security trixie-security/main amd64 nginx amd64 1.26.3-3+deb13u10 [614 kB]
Get:3 https://deb.debian.org/debian-security trixie-security/main amd64 nginx-common all 1.26.3-3+deb13u10 [112 kB]
Unpacking nginx (1.26.3-3+deb13u10) over (1.26.3-3+deb13u7) ...
Unpacking nginx-common (1.26.3-3+deb13u10) over (1.26.3-3+deb13u7) ...
Setting up nginx-common (1.26.3-3+deb13u10) ...
Setting up nginx (1.26.3-3+deb13u10) ...
Upgrading binary: nginx.
Do you need to restart nginx after the upgrade? No. Upgrading binary (Ubuntu prints * Upgrading binary nginx) means the package’s post-install script started the new binary and handed it the listening sockets. You can prove the old code is gone: the master PID changed on every lab server (29278 to 30333 on Ubuntu 26.04, 2042 to 3177 on 24.04, 15873 to 16469 on Debian 13), and the test site went on returning the same 403 on port 8080 straight after (its document root has no index file). A server still running the pre-patch master is still vulnerable, whatever dpkg says, so this check is worth the ten seconds.
Confirm all three things at once: the package revision, the CVE entries in the changelog, and a master process that started after the upgrade:
dpkg-query -W -f='${Package} ${Version}\n' nginx nginx-common
zgrep -E 'CVE-2026-(93479|15176|97205|95101|97629)' /usr/share/doc/nginx-common/changelog.Debian.gz
ps -o pid,lstart,cmd -C nginx | head -2
sudo nginx -t
Ubuntu 26.04 after the update. All five CVEs appear as separate patches, and the master process started at the moment apt ran:
nginx 1.28.3-2ubuntu1.12
nginx-common 1.28.3-2ubuntu1.12
- debian/patches/CVE-2026-15176.patch: Avoid post-rewrite URI to path
- CVE-2026-15176
- debian/patches/CVE-2026-93479.patch: Script: reserved memory overrun
- CVE-2026-93479
- debian/patches/CVE-2026-95101.patch: HTTP/3: kept the skipped frame length
- CVE-2026-95101
- debian/patches/CVE-2026-97205.patch: Limited the number of pending
- CVE-2026-97205
- debian/patches/CVE-2026-97629.patch: SSL: synced all session ticket keys
- CVE-2026-97629
PID STARTED CMD
30333 Tue Oct 27 01:34:39 2026 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Ubuntu 24.04’s changelog lists four of the five, without CVE-2026-95101. That’s expected rather than a missed patch: the HTTP/3 bug only applies to 26.04 and 26.10 in USN-8955-1. Debian 13’s changelog lists all five and says they were backported from upstream 1.30.6. If you need the background on how these packages are laid out, the nginx install guide for Ubuntu 26.04 and the Ubuntu 24.04 and Debian 13 version use the same distro packages patched here.

The capture above is the same check on the 26.04 server, with the fixed revision and the CVE lines from the package changelog side by side.
Amazon Linux 2023 needs a newer snapshot
Amazon Linux 2023 pins each instance to a dated repository snapshot, so dnf cannot see a fix released after your snapshot until you point it at a newer one. The lab container was on 2023.12.20261021 with nginx 1.30.5, and a plain advisory query came back empty even though ALAS2023-2026-3263 had been out for days. That empty result is the trap: it looks like “not affected” when it means “not visible yet”.
cat /etc/amazon-linux-release
rpm -q nginx
sudo dnf updateinfo list --cve CVE-2026-93479 --releasever 2023.12.20261026
With the advisory’s release version passed explicitly, the fix shows up as three packages:
Amazon Linux release 2023.12.20261021 (Amazon Linux)
nginx-1.30.5-1.amzn2023.0.1.x86_64
ALAS2023-2026-3263 Medium/Sec. nginx-1:1.30.6-1.amzn2023.0.1.x86_64
ALAS2023-2026-3263 Medium/Sec. nginx-core-1:1.30.6-1.amzn2023.0.1.x86_64
ALAS2023-2026-3263 Medium/Sec. nginx-filesystem-1:1.30.6-1.amzn2023.0.1.noarch
Amazon rates the bundle Medium, and dnf updateinfo info ALAS2023-2026-3263 lists all five CVEs. Apply only that advisory, which keeps the rest of the instance on its current snapshot:
sudo dnf update -y --advisory ALAS2023-2026-3263 --releasever 2023.12.20261026
All three packages moved to 1.30.6-1.amzn2023.0.1, and the binary reports the upstream fixed version because Amazon rebased instead of backporting:
Upgraded:
nginx-1:1.30.6-1.amzn2023.0.1.x86_64
nginx-core-1:1.30.6-1.amzn2023.0.1.x86_64
nginx-filesystem-1:1.30.6-1.amzn2023.0.1.noarch
Complete!
nginx-1.30.6-1.amzn2023.0.1.x86_64
nginx version: nginx/1.30.6
The Amazon package’s post-uninstall scriptlet calls /usr/bin/nginx-upgrade on upgrade, the same in-place binary swap Debian does. The lab ran in a container without systemd, so check the master’s start time with ps -o pid,lstart,cmd -C nginx on a real instance and run sudo systemctl restart nginx if it predates the update. If you built the server from the WordPress on Amazon Linux 2023 guide, this is the nginx package it installed.
RHEL clones and Debian 12 wait
Red Hat lists RHEL 8, 9 and 10 as affected by CVE-2026-15176 with “a fix is being pursued”, and no RHSA existed on 27 October 2026, so Rocky Linux and AlmaLinux have nothing to rebuild yet. Rocky Linux 10.2’s AppStream still offered nginx-2:1.26.3-6.el10_2.7 in the lab. Debian 12 is in the same position: the security tracker marks bookworm’s 1.22.1-9+deb12u10 as vulnerable to both CVEs, and DSA-6575-1 covers trixie only. You have two choices on these systems:
- apply the config mitigations in the next section and wait for the vendor package
- switch to nginx.org’s own packages, which already ship 1.30.6
Red Hat lists no mitigation of its own for CVE-2026-15176, so on these hosts the F5 config changes are the stopgap and the nginx.org package is the only fixed binary. Switching is a bigger change than you might expect. The nginx.org build has a different file layout, ships its own conf.d/default.conf, and leaves you outside Red Hat’s update stream until you switch back. If your box matches the trigger shapes and you cannot change its config quickly, it is still the fastest way to a fixed binary. The Rocky Linux and AlmaLinux nginx setup uses the AppStream package, which is what you’d be replacing.
Back up your current configuration first, because removing the AppStream packages moves your nginx.conf aside:
sudo tar czf /root/nginx-etc-backup.tar.gz /etc/nginx
Create the repository file nginx.org documents for RHEL and its rebuilds:
sudo vim /etc/yum.repos.d/nginx.repo
Paste the stable repository definition. $releasever and $basearch are dnf variables, so leave them as written:
[nginx-stable]
name=nginx stable repo
baseurl=https://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
A plain dnf upgrade nginx does not work from here, and it’s the first thing most people try. Both packages carry epoch 2, so dnf picks 1.30.6 as an upgrade, then the transaction test fails because the AppStream split puts the binary in nginx-core:
Error: Transaction test error:
file /etc/logrotate.d/nginx from install of nginx-2:1.30.6-1.el10.ngx.x86_64 conflicts with file from package nginx-core-2:1.26.3-6.el10_2.7.x86_64
file /etc/nginx/nginx.conf from install of nginx-2:1.30.6-1.el10.ngx.x86_64 conflicts with file from package nginx-core-2:1.26.3-6.el10_2.7.x86_64
file /usr/sbin/nginx from install of nginx-2:1.30.6-1.el10.ngx.x86_64 conflicts with file from package nginx-core-2:1.26.3-6.el10_2.7.x86_64
file /var/log/nginx from install of nginx-2:1.30.6-1.el10.ngx.x86_64 conflicts with file from package nginx-core-2:1.26.3-6.el10_2.7.x86_64
Adding --allowerasing failed the same way in the lab, because dnf still kept nginx-core as a separate package with the same conflicting files. Remove the AppStream packages, then install from the new repository. The three-package list below is what Rocky Linux 10 installs; on 8 and 9 nginx comes from a module stream and may bring nginx-mod-* or nginx-all-modules, so run rpm -qa 'nginx*' first and remove what it lists. Before you do, check that every dynamic module you load has an nginx.org equivalent, because the AppStream module packages go with the rest. This takes nginx down for the few seconds between the two commands, so do it in a maintenance window or behind a load balancer:
sudo dnf -y remove nginx nginx-core nginx-filesystem
sudo dnf -y install nginx
dnf imports the nginx signing keys (the main one has fingerprint 573B FD6B 3D8F BC64 1079 A6AB ABF5 BD82 7BD9 BF62, which nginx.org publishes) and installs nginx-2:1.30.6-1.el10.ngx. The removal saved your edited nginx.conf as nginx.conf.rpmsave, while conf.d/ kept your site files. Put your main config back and test it:
sudo cp /etc/nginx/nginx.conf.rpmsave /etc/nginx/nginx.conf
sudo nginx -t
nginx -v
The restored Rocky config passed the syntax check under the nginx.org binary:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
nginx version: nginx/1.30.6
That test ran in a Rocky Linux 10.2 container, so two things were not exercised, and one file needs your attention. After the switch on a real host, check these:
sudo systemctl enable --now nginx, since removing the old packages can disable the unitsudo ausearch -m avc -ts recentafter the first requests on SELinux-enforcing hosts- delete
/etc/nginx/conf.d/default.confif your own server blocks already own port 80
For Debian 12, nginx.org publishes a bookworm repository on the same Linux packages page; the lab did not test it, so the mitigation below is the tested path there.
Config changes close the documented paths
Each advisory names its own workaround, and both target the trigger shape instead of disabling a module. For CVE-2026-93479, F5’s K000162607 says to stop using unnamed captures and use named captures limited to the block with the regex match. For CVE-2026-15176, K000162416 says to avoid alias in a location and avoid the $request_filename variable. For the AIO use-after-free, K000162608 says to disable either AIO or SSI. In order of how much they change:
- named captures inside
map, read only through the map’s output variable $document_root$uriin place of$request_filenamerootplus a prefix-strippingrewritein place ofaliasaio off(orssi off) where both are enabled
The lab’s cve-lab2.conf had every trigger in one file, which makes it a fair test of whether the rewrite still serves the same URLs. Open the file that the detection step flagged on your server:
sudo vim /etc/nginx/conf.d/cve-lab2.conf
This is the full rewritten file as it ran on the Ubuntu 26.04 server. The map captures into the named group $thumb and only the map output $thumb_name leaves the block, so nothing outside the map reads a regex capture. The log format builds the path from $document_root$uri, /thumbs/ uses a fixed root, and /dl/ maps onto /srv/files with root and two rewrites instead of alias:
map $uri $thumb_name {
~^/thumbs/(?<thumb>.+)\.jpg$ $thumb;
default "";
}
log_format fileaudit '$remote_addr $status "$document_root$uri" thumb=$thumb_name';
server {
listen 8081;
server_name files.example.com;
access_log /var/log/nginx/files.log fileaudit;
location /thumbs/ {
root /srv;
}
location /dl/ {
root /srv/files;
rewrite ^/dl/v1/(.*)$ /dl/$1;
rewrite ^/dl/(.*)$ /$1 break;
}
}
The first rewrite has no flag, so a /dl/v1/ request falls through to the second, which strips /dl/ and stops. Both URL forms end up at the same file under /srv/files, which is what the old alias was meant to do. Test, reload and request all three paths:
sudo nginx -t && sudo systemctl reload nginx
for u in /dl/report.txt /dl/v1/report.txt /thumbs/cat.jpg; do curl -s -o /dev/null -w "%{http_code} %{size_download}\n" http://127.0.0.1:8081$u; done
sudo tail -n 3 /var/log/nginx/files.log
All three requests returned their test files, and the log shows the resolved paths plus the map output, with no $request_filename and no numbered capture anywhere:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
200 17
200 17
200 8
127.0.0.1 200 "/srv/files/report.txt" thumb=
127.0.0.1 200 "/srv/files/report.txt" thumb=
127.0.0.1 200 "/srv/thumbs/cat.jpg" thumb=cat
The /dl/v1/ line is the interesting one. With the original alias location on the same server, that URL returned 500 while /dl/report.txt returned 200, so the rewrite fixed a broken URL as well as removing the trigger. That 500 comes with the error below (this copy was captured on the Debian 13 server), which you may already have in your logs.
Error: “alias” cannot be used in location “/dl/” where URI was rewritten
With the original alias plus rewrite location, /dl/v1/report.txt returned HTTP 500 and nginx logged this alert, along with a second copy ending in while logging request. It’s nginx refusing to map a rewritten URI through alias, and it is not caused by the security update. On Debian 13 the same three URLs went to both test server blocks before and after the upgrade, and all six responses matched on the vulnerable 1.26.3-3+deb13u7 and the fixed 1.26.3-3+deb13u10. The alert below was captured on the fixed build. In the lab, then, the patch changed no responses, so you can install it without expecting new errors.
[alert] 17184#17184: *3 "alias" cannot be used in location "/dl/" where URI was rewritten, client: 127.0.0.1, server: files.example.com, request: "GET /dl/v1/report.txt HTTP/1.1", host: "files.example.com"
If this alert shows up in your error.log, you have an alias plus rewrite location, and URLs in it are already broken for users. That alone isn’t the CVE-2026-15176 trigger, which also needs $request_filename in a set, return or log format, but it tells you exactly which location to check next. The root conversion above fixes both problems at once.
Turn off AIO where SSI is on
CVE-2026-97205 needs AIO and an SSI page with include virtual, so turning off either one removes the path. F5 notes that disabling AIO can cost throughput when you stream large static files under heavy concurrency. Proxies and API gateways barely notice. Changing aio threads; to aio off; in the lab config passed nginx -t and reloaded cleanly on all three servers. Keep the change until the package update is in, then decide whether you want AIO back.
One thing these edits don’t cover is the second path on CVE-2026-93479, the undefined “non-cacheable variable” case, and neither of the other companion bugs. For anything internet-facing, the mitigation is a bridge to the package update, not a replacement for it. If you also front PHP apps with nginx, the PHP2Shell mitigation guide covers the same “patch later, block now” approach for PHP-FPM.
Each CVE in one table
Use this as your review sheet when you go through a fleet: the config shape that exposes a server, the change that removes it, and whether patching is the only fix. Trigger shapes come from the F5 and Amazon advisory text; the upstream fix for all five is nginx 1.30.6 or 1.31.7:
| CVE | Config that exposes you | Change if you can’t patch |
|---|---|---|
| CVE-2026-93479 | map regex whose numbered captures are used in root or a log format | Named captures, read through the map output only |
| CVE-2026-15176 | alias + rewrite + set/return with $request_filename, or that variable in log_format | root instead of alias, $document_root$uri instead of $request_filename |
| CVE-2026-97205 | aio on and SSI pages using include virtual | aio off or ssi off |
| CVE-2026-95101 | HTTP/3 listener with proxy_request_buffering on | Patch |
| CVE-2026-97629 | ssl_session_cache shared without an ssl_session_ticket_key file | Patch |
F5 also lists NGINX Plus, its own NGINX Ingress Controller and NGINX Gateway Fabric as affected, each with its own fixed build. Those products carry nginx inside their images, so patching the host OS doesn’t touch them: they need a chart or image update. The community ingress-nginx controller isn’t in F5’s tables, so check that project’s release notes separately instead of assuming either way.