SevyDevy

Guide · Backend

NGINX's October 2026 Vulnerabilities Explained: Five Config Patterns to Audit

18 min readUpdated By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

  • The nginx vulnerability October 2026 release is nginx 1.30.6 (stable branch, important fixes only) and 1.31.7 (mainline branch, which also gets features), out on 14 October 2026. It fixes five CVEs, the public IDs given to security flaws.
  • Each flaw needs a particular config to reach it. Two involve URL handling: alias (URL-to-folder mapping) with rewrite (a URL-changing rule), and maps that match by regex (text patterns). The others need aio (async file reads) with server side includes (pages built from pieces), HTTP/3 (the newest HTTP version), or automatic TLS session ticket keys (for resuming connections).
  • nginx labels all five Medium. F5, the company behind nginx that issued the CVE records, scores three of them 9.2 out of 10 on CVSS v4.0, a standard severity scale. Report both.
  • To check a server, nginx -v shows its version and nginx -T prints the whole config it would load. Search that output for the five patterns. Distribution packages can be patched without a new version number.
  • Beginners can stop after the section on the two severity ratings. Engineers then get each bug's mechanism checked in the 1.30.4 source, a tested audit script, workaround costs and a backport check.

What goes wrong when a normal nginx config meets a memory bug?

A config line you wrote years ago can switch on code that has a memory or parsing bug. Crafted requests can then crash an nginx worker, and for some bugs possibly run code. That is the story of the nginx vulnerability October 2026 release: five bugs, each reachable only through a particular config pattern.

Picture a common setup. A grocery delivery company sends every request, about 3 lakh (300,000) orders a day, through four nginx servers running nginx 1.30.4. The setup is made up, but every config pattern in it is real.

The team's config has grown for five years. It maps old offer links to a folder with alias and rewrite, and picks a city folder with a regex map. It builds help pages from fragments, serves HTTP/3 to the app and asks delivery partners' phones for client certificates.

On 15 October the team has to decide whether it is affected. That depends on the config, so this guide checks it one pattern at a time.

Which words do you need before reading an nginx security advisory?

  • Worker process: nginx runs one master process and a few workers that handle requests. A crashed worker is restarted, but the requests it was handling fail.
  • Heap buffer overflow: code writes past the end of a memory block it got while running. Use-after-free: code keeps using memory it already gave back.
  • ASLR: address space layout randomisation, an operating system defence that places memory at random addresses so an attacker cannot predict where things are.
  • CVE and CVSS: a CVE is the public ID for one flaw. CVSS is a standard score from 0 to 10 for how bad a flaw could be.
  • Directive: one instruction line in nginx.conf, such as alias or aio. A config pattern here means the mix of directives that reaches the buggy code.
  • Stable and mainline: nginx's two supported branches. Stable (1.30) gets only important fixes, while mainline (1.31) also gets new features.

Which flaws does the nginx vulnerability October 2026 release fix?

It fixes five flaws, all listed on nginx's security advisories page with severity Medium. The table maps each flaw to the config that exposes you, with the version ranges from that page. Each directive in it is explained in that bug's own section below.

The five CVEs fixed in nginx 1.30.6 and 1.31.7, as of 19 October 2026 (nginx rates every one Medium)
CVEWhat goes wrongConfig pattern that exposes youVulnerable versionsF5 CVSS v4.0 (v3.1)
CVE-2026-15176Heap buffer overflow in a workeralias in a location, a rewrite that changes the URI (the URL path), and set, return or an access log format reading $request_filename0.1.29 to 1.31.69.2 (8.1)
CVE-2026-93479Heap buffer overflow in a workerA regex map whose captures are read by root or a log format before the map's own variable. F5 also names a second path.1.30.0 to 1.30.5 and 1.31.2 to 1.31.69.2 (8.1)
CVE-2026-97205Use-after-free in a workeraio switched on, plus SSI pages that use include virtual0.8.11 to 1.31.69.2 (8.1)
CVE-2026-95101Data injected into the proxied request, or a body larger than client_max_body_sizeHTTP/3 with proxy_request_buffering on, which is the default1.25.0 to 1.31.66.9 (5.3)
CVE-2026-97629Forged TLS session that may skip the client certificate checkssl_session_cache shared without ssl_session_ticket_key, with session tickets on (the default)1.23.2 to 1.31.68.3 (6.5)

The team's servers run 1.30.4, which sits inside every range. All five patterns appear in its config, so each one needs a closer look.

1.30.4 also misses an earlier fix. nginx 1.30.5 and 1.31.6, released on 15 September 2026, fixed CVE-2026-90439, a heap overflow when using HTTP/3 with OpenSSL 3.5.0 and earlier. Moving to 1.30.6 picks up both.

Why does nginx rate these flaws Medium when F5 scores them 9.2?

The two numbers come from different systems, and neither is wrong. nginx puts its own severity label on its advisories page. F5, the company behind nginx, issued the CVE records with CVSS scores.

CVSS rates the worst case on a vulnerable setup. F5's records say the three memory bugs may allow code execution where ASLR is disabled or can be bypassed. Those three score 9.2 on CVSS v4.0, or 8.1 on v3.1.

The scores already include how hard an attack is. All three 9.2 vectors mark attack complexity High (AC:H), and none needs a login. The HTTP/3 bug scores 6.9 because F5 rates its impact as limited integrity only (VI:L).

How does the nginx alias and rewrite buffer overflow work?

After a rewrite, nginx can build a file path in a buffer that is too small and write past its end. Three things must meet: alias, a rewrite that changes the URI (the path part of the URL), and a read of $request_filename.

alias swaps a URL prefix for a folder. With location /seasonal-offers/ and alias /srv/offers/, a request for /seasonal-offers/diwali.html reads /srv/offers/diwali.html. $request_filename holds that final file path.

In the 1.30.4 source, the function that builds this path sizes it using the URI length minus the location prefix length. The Hacker News, describing the fix, says the overflow happens when a rewrite has made the URI shorter than that prefix, before nginx picks the next location.

F5's record names set, return and an access log format as the readers. The team rewrites old offer links to short paths like /o/42 and logs $request_filename, so it matches.

How can an nginx regex map with captures overflow a buffer?

A regex map can overflow a buffer when other directives read its captures before its own variable. nginx's change log says the bug was in evaluating string expressions with variables, used for the document root and request logging in the http and stream modules.

A map sets one variable from another through a lookup table. A key starting with ~ is a regular expression, a text pattern, and its captures become variables like $1 or a named $city. The team maps each city's host name to a folder this way.

Per F5's record, the risky order is root (which sets the files folder) or a log format reading the captures before the map's own variable. The record also names a second path: a non-cacheable variable in a string expression, under conditions it does not spell out.

The Hacker News reports that this bug sits in code nginx changed in July for CVE-2026-42533, an earlier map flaw fixed in 1.30.4. So the team's July upgrade to 1.30.4 did not cover this one.

Why does nginx's async file I/O with SSI cause a use-after-free?

A small counter wraps around, so nginx frees a request while file reads still point at it. It needs aio switched on together with SSI pages that use include virtual.

aio lets a worker start a file read and keep serving others while it waits. It is off by default. SSI, or server side includes, builds a page from pieces, and include virtual pulls in the output of another request.

In the 1.30.4 source, a request's pending async operations are counted in an 8-bit field called blocked, which holds 0 to 255. Both aio on and aio threads add to it. Async reads made inside include virtual subrequests count on the main request's field, so many included pieces share one 8-bit counter. nginx frees the request once this field and the request count are both zero.

The Hacker News reports there was no bounds check, and that parallel include virtual requests are the trigger. nginx's SSI docs confirm that included requests handled by proxied or FastCGI servers run in parallel.

python
# A toy model of the counter, not nginx code: 8 bits hold 0 to 255
pending_reads = 256   # async reads waiting at the same moment (made up)

blocked = 0
for _ in range(pending_reads):
    blocked = (blocked + 1) % 256   # what an 8-bit field does when it overflows

print("Reads still pending:", pending_reads)
print("Counter says:", blocked)
print("Looks finished, so freed too early:", blocked == 0)

Run it and you get three lines: 256 reads pending, a counter of 0, and True. A counter of 0 looks like nothing is pending. The team's help pages pull in many fragments, so this one is marked reachable. The code dates back to 0.8.11, when aio appeared.

How does the nginx HTTP/3 bug slip past the request body size limit?

Part of an unknown HTTP/3 frame can be treated as request body. nginx's change log says this can bypass client_max_body_size, your upload size limit, or inject data into the request sent to your backend.

HTTP/3 sends data in frames, and a server skips frame types it does not know. In the 1.30.4 source, one length field tracks both the bytes left to skip and the body bytes left to read. The Hacker News reports that an unknown frame split across two reads leaked its leftover bytes into the body.

You are exposed only if you serve HTTP/3, usually through a listen line with quic, the transport HTTP/3 runs on. F5's record also requires proxy_request_buffering on, the default, where nginx reads the whole body before passing it on.

The HTTP/3 module is experimental and not built by default, so check nginx -V for --with-http_v3_module. Packaged builds differ, so check the binary you actually run rather than assume. The team's app uses HTTP/3, so this one is reachable.

How can TLS session tickets be forged when nginx rotates keys itself?

When nginx rotates ticket keys by itself, one key slot was left uninitialised, which might let an attacker forge a session. A forged session may skip the client certificate check.

A full TLS handshake, the opening exchange that sets up encryption, is slow, so servers let clients resume. The server seals the session details in a ticket, encrypted with a key only it knows. The client hands it back next time, like a cloakroom token.

Where the picture breaks: a cloakroom has one token book, while nginx keeps a few keys and uses the one whose name the ticket carries. The bug was in one of those keys, not in the ticket format.

Since 1.23.2, a shared session cache makes nginx generate and rotate ticket keys itself, unless you set ssl_session_ticket_key. In the 1.30.4 source, each worker keeps three key slots, but the copy from shared memory fills only two.

How a forged ticket skips the certificate check

Notice that a resumed session skips the full handshake, client certificate check included. A ticket nginx wrongly accepts skips that check too.

Diagram connections:

  • Full handshake to Ticket issued
  • Ticket issued to Ticket sent back
  • Ticket sent back to A key slot opens it?
  • Forged ticket to A key slot opens it?
  • A key slot opens it? to Session resumed (Yes)
  • A key slot opens it? to Full handshake again (No)
  • Full handshake: Partner app → nginx: sends its client certificate
  • Ticket issued: nginx → partner app: session sealed with a ticket key
  • Ticket sent back: Partner app → nginx: asks to resume the session
  • Forged ticket: Attacker → nginx: ticket that a bad slot accepts
  • A key slot opens it?: nginx: finds the ticket's key among its key slots
  • Session resumed: nginx → client: certificate check skipped
  • Full handshake again: nginx → client: certificate checked again

F5's CVE record calls the forgery non-deterministic and beyond the attacker's control. It still matters for the team, because delivery partners' phones prove who they are with client certificates, a digital ID, before payouts. Servers with ssl_session_tickets off are not affected.

How do you check your nginx config for these flaws with nginx -T?

Check the version and build first, then dump the config with nginx -T and search it. -T tests the config and prints every file it loads, so patterns hidden in included files show up too.

  1. Check the version

    Run nginx -v 2>&1 on every server, because nginx prints the version to stderr. Upstream 1.30.6 or later on stable, or 1.31.7 or later on mainline, is fixed.

  2. Check the build

    Run nginx -V 2>&1 and look for --with-http_v3_module. Without it, the HTTP/3 bug cannot reach you.

  3. Dump the config

    Run sudo nginx -T and save the output from each server, because configs drift. If the config test fails, nothing is dumped, so fix the error first.

  4. Search and read

    Run the script below on each dump. Then read every flagged line inside its own block, because directives in different blocks may not combine.

bash
# 1. Version: nginx prints it to stderr, hence 2>&1
nginx -v 2>&1
# Example output on Ubuntu 24.04: nginx version: nginx/1.24.0 (Ubuntu)

# 2. HTTP/3 module built in? No output means no.
nginx -V 2>&1 | grep -o -- --with-http_v3_module

# 3. Dump the full config, then run the audit script on it
sudo nginx -T > full.conf
python3 audit_nginx.py full.conf
python
import re
import sys

# Usage: sudo nginx -T > full.conf, then: python3 audit_nginx.py full.conf
text = open(sys.argv[1]).read()
lines = [re.sub(r"(^|\s)#.*", "", line) for line in text.splitlines()]  # drop comments

def find(pattern):
    return [n for n, line in enumerate(lines, 1) if re.search(pattern, line)]

checks = {
    "CVE-2026-15176 alias, rewrite, $request_filename":
        [r"\balias\s", r"\brewrite\s", r"\$request_filename\b"],
    "CVE-2026-93479 map with a regex key": [r"(^|[{;])\s*[\"']?~"],
    "CVE-2026-97205 aio with ssi": [r"\baio\s+(on|threads)", r"\bssi\s+on\b"],
    "CVE-2026-95101 HTTP/3 listener": [r"\blisten\s[^;]*\bquic\b"],
    "CVE-2026-97629 shared session cache": [r"\bssl_session_cache\s[^;]*shared:"],
}
for name, patterns in checks.items():
    hits = [find(p) for p in patterns]
    if all(hits):
        print("READ BY HAND", name, "lines", sorted(set(sum(hits, []))))
    else:
        print("not found   ", name)

exempt = find(r"\bssl_session_ticket_key\s|\bssl_session_tickets\s+off")
print("Session ticket exemptions to check per server, lines", exempt)

On a test config with four patterns spread over the main file and one included file, nginx 1.24.0 dumped both files. The script printed READ BY HAND for alias, map, aio and the session cache, and not found for HTTP/3.

The script over-reports on purpose and cannot see the second CVE-2026-93479 path. Do not let one exemption clear the whole file: ssl_session_tickets off in one server says nothing about the server next to it.

nginx -T reads the files on disk now. If they changed since the last reload, the running workers may still use an older config than the dump shows.

The audit flow for each server

Notice that the version check comes first, and that a clean config still ends in an upgrade.

Diagram connections:

  • Run nginx -v 2>&1 to Fix confirmed?
  • Fix confirmed? to Fixed (Yes)
  • Fix confirmed? to Run sudo nginx -T (No)
  • Run sudo nginx -T to Run the audit script
  • Run the audit script to Pattern found?
  • Pattern found? to Apply F5 workaround (Yes)
  • Pattern found? to Upgrade anyway (No)
  • Run nginx -v 2>&1: Shows the version of the installed binary
  • Fix confirmed?: 1.30.6+ or 1.31.7+, or all five CVEs in changelog
  • Fixed: Keep the audit output with the ticket
  • Run sudo nginx -T: Prints every config file nginx would load
  • Run the audit script: alias, map, aio with ssi, quic, session cache
  • Pattern found?: Read each flagged line in its own block
  • Apply F5 workaround: Then schedule the upgrade
  • Upgrade anyway: No exposed pattern today, but configs change

How do you tell if a distribution's nginx package is patched?

Read the package changelog, not the version number. Distributions often backport fixes into an old version, so nginx -v cannot tell you whether these five are patched.

On Ubuntu 24.04, as of 19 October 2026, the current package prints nginx/1.24.0. Its changelog lists July's CVE-2026-42533 fix but none of the five October CVEs.

bash
# Debian-family packages (tested on Ubuntu 24.04): which October CVEs are patched?
zcat /usr/share/doc/nginx/changelog.Debian.gz \
  | grep -oE "CVE-2026-(15176|93479|97205|95101|97629)" | sort -u
# No output on Ubuntu 24.04's 1.24.0-2ubuntu7.18 (19 October 2026): none patched yet

Ubuntu's nginx changelog names the CVEs it patches, so once a fix lands the command prints its ID. Ubuntu 26.04's package, 1.28.3-2ubuntu1.11, also lists none of the five as of 19 October 2026. Debian's tracker lists all five fixed in testing and unstable (1.30.4-8), with no fix yet for bookworm or trixie.

How do you upgrade nginx or work around each flaw today?

Upgrade to nginx 1.30.6 or 1.31.7, the only upstream releases with all five fixes. Until then, F5's workarounds close each path, each with a cost.

F5 workarounds from the CVE records, and their side effects
CVEWorkaround until you upgradeSide effect to plan for
CVE-2026-15176Avoid alias in that location, or avoid reading $request_filenameRewriting the location with root changes how paths map, so test old links
CVE-2026-93479Use named captures instead of $1-style ones, and use them only inside the block with the regex matchA stopgap: in July, a researcher told The Hacker News that similar named-capture advice left a narrower path open
CVE-2026-97205Turn aio off, or stop serving SSI pagesSlow disk reads can block a worker again
CVE-2026-95101Stop serving HTTP/3 by removing quic from listen lines. F5's own workaround, for proxy_pass locations, is proxy_request_buffering off.Without HTTP/3, clients connect over TCP instead. With buffering off, nginx cannot retry another backend once it starts sending the body.
CVE-2026-97629Set ssl_session_ticket_key, or remove the shared cache. Turning tickets off also avoids it.You rotate keys yourself, or more clients do full handshakes

As of 19 October 2026, nginx publishes fixes only for 1.30 and 1.31. Four of the five ranges include 1.28, so a server on an older upstream branch must move to a supported one or rely on a distribution backport.

A 1.28 server has missed more than these five. No 1.28 release has followed 1.28.3, so it also lacks May's rewrite fix CVE-2026-42945 and July's map fix CVE-2026-42533. The Hacker News reports the May flaw was exploited within days of disclosure.

Roll out one server at a time: take it out of the load balancer, upgrade, run nginx -t and watch the error log. Afterwards, nginx -v 2>&1 should print nginx version: nginx/1.30.6 on stable.

Expect a quieter error log. The 1.30.6 change log lowers some SSL error messages from crit to info, so fewer crit lines is not a sign of trouble.

How do you explain the nginx October 2026 vulnerabilities in a system design interview?

Tell it as a story about config-dependent risk and safe patching. The five steps below give you the frame, from trigger to rollout.

  1. Name the trigger

    Each bug needs a config pattern, such as a shared session cache without a ticket key.

  2. Separate the scores

    nginx says Medium, while F5's CVSS v4.0 says up to 9.2. CVSS rates the worst case on a vulnerable setup.

  3. Show the audit

    Check versions with nginx -v, dump configs with nginx -T and search for the patterns. For distribution packages, read the changelog.

  4. Contain, then roll out

    Apply the workaround tonight, then upgrade one server at a time behind the load balancer.

  5. Add depth

    Keep size and certificate checks in the backend too, so one proxy bug is not enough.

Questions people ask

Is my nginx from Ubuntu or Debian packages affected?
It depends on whether your distribution backported the fixes, and nginx -v cannot tell you. Search the package changelog for the five CVE IDs. As of 19 October 2026, Ubuntu 24.04 and 26.04 packages list none of them, and Debian has fixes only in testing and unstable.
Why does nginx call these Medium when CVSS says 9.2?
They are different ratings. nginx sets its own label on its advisories page, while F5's CVSS v4.0 score rates the worst case on a vulnerable setup. Three bugs may allow code execution where ASLR is off or bypassed. Report both, and let your audit set the urgency.
Are these nginx bugs being exploited?
No attacks were reported as of 15 October 2026, per The Hacker News. As of 19 October, none of the five is in the US agency CISA's Known Exploited Vulnerabilities catalogue. The same outlet reports a May nginx flaw was exploited within days of disclosure, so do not wait.
Will nginx 1.28 get a fix?
No upstream fix exists for 1.28 as of 19 October 2026. nginx publishes fixes only for the 1.30 stable and 1.31 mainline branches. Four of the five vulnerable ranges include 1.28, so move to 1.30.6 or later, or use a distribution that backports patches.
Do I need to act if I do not use HTTP/3?
Yes, because only CVE-2026-95101 needs HTTP/3. The other four depend on alias, map, aio and session cache settings. Check those in your nginx -T output and plan the upgrade either way.
Are NGINX Plus and the Ingress Controller covered by this release?
No, they have their own F5 advisories. The open-source release covers nginx 1.30.6 and 1.31.7 only. The Hacker News reported that several F5 products had no fixed build as of 15 October 2026, so check each product's advisory.

Sources and further reading

Practise this with Lala

Practise