NGINX Gateway Fabric 2.8 adds AccessPolicy, which allows or denies traffic to a Gateway or route by client IP address. Passive health checks in UpstreamSettingsPolicy let you decide how many failed attempts take an upstream server out of rotation and for how long, and cookie-based session persistence now works on NGINX Open Source. NGINX Gateway Fabric can also create Prometheus ServiceMonitors for its control plane and for each Gateway’s data plane, and send access logs to a file or a syslog server instead of only stdout. Gateway API Inference Extension users can verify the TLS connection to their endpoint picker with a BackendTLSPolicy.
How it runs got attention too. Rolling upgrades send configuration only to pods running the expected image, an optional Secret label selector keeps the control plane from caching Secrets your Gateways never use, and Kubernetes watches stay open instead of reconnecting every 10 seconds. Support for nginx.org/htpasswd Secrets is gone, and NGINX Gateway Fabric no longer sets loadBalancerClass on its own — read the upgrade notes before you move to 2.8. Active health checks, a larger default upstream zone, and a few fixes need an F5 NGINX Enterprise solution, and they’re collected in one section at the end. Here’s what’s new.
Release highlights
- IP allow and deny lists: Allow or deny traffic to a Gateway or route by client IP address, without an NGINX snippet.
- Passive health checks: Decide how many failed attempts take an upstream server out of rotation, and for how long.
- Cookie-based session persistence in NGINX Open Source: Keep each user on the same backend, even when their IP address changes.
- Prometheus ServiceMonitors: Scrape the control plane and every Gateway’s data plane without writing ServiceMonitors by hand.
- Bug fixes and smaller enhancements: Updates to upstream keepalive and server ordering, deleted endpoints, Gateway addresses and status, data plane recovery, certificate and BackendTLSPolicy validation, inference routing through the endpoint picker, basic authentication Secrets, load balancer classes, and the Helm chart.
IP allow and deny lists with AccessPolicy
What’s new: A new AccessPolicy resource controls which client IP addresses can reach a Gateway, an HTTPRoute, or a GRPCRoute. Each policy sets an action of Allow or Deny and up to 64 rules, each matching an IP address or an address range such as 192.0.2.0/24. NGINX Gateway Fabric turns them into NGINX allow and deny directives, and a blocked request gets a 403 Forbidden response. An Allow policy rejects any request it doesn’t match. A Gateway’s policy covers every route attached to it, and Deny rules from the Gateway and the route both apply. When the Gateway and a route both have Allow rules, the route’s can only narrow the Gateway’s, so a request has to fall inside both ranges (nginx/nginx-gateway-fabric#6063).
apiVersion: gateway.nginx.org/v1alpha1
kind: AccessPolicy
metadata:
name: coffee-allowlist
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: coffee
action: Allow
rules:
- name: office-network
source:
type: IPAddress
ipAddress:
address: 192.0.2.0/24
AccessPolicy matches each request’s client IP address. If a load balancer or proxy sits in front of NGINX, configure rewriteClientIP on the NginxProxy so the rules see the original client address rather than the proxy’s.
Why it matters: Limiting an internal tool to your office network, or blocking an address range you don’t trust, becomes a policy the API server validates and NGINX Gateway Fabric reports status on, rather than an NGINX snippet. Because a route’s Allow rules can’t reach past the Gateway’s, a platform team can set the outer boundary on the Gateway and leave each application team to tighten it for their own routes.
Passive health checks for your upstreams
What’s new: UpstreamSettingsPolicy gains a healthCheck field. Its passive settings map to the NGINX max_fails and fail_timeout parameters: maxFails sets how many failed attempts within failTimeout mark an upstream server unavailable, and failTimeout also sets how long the server stays out of rotation. They apply to the HTTP, HTTPS, and gRPC upstreams of the Services the policy targets, and maxFails: 0 stops NGINX from counting failed attempts (nginx/nginx-gateway-fabric#6000).
apiVersion: gateway.nginx.org/v1alpha1
kind: UpstreamSettingsPolicy
metadata:
name: example-passive-hc
spec:
targetRefs:
- group: core
kind: Service
name: coffee
healthCheck:
passive:
maxFails: 3
failTimeout: 5s
Why it matters: NGINX already takes a server out of rotation after one failed attempt in 10 seconds. UpstreamSettingsPolicy lets you tune that per Service: tolerate a few errors from a backend that’s slow to warm up, or keep a failing server out longer. Passive checks act on live traffic, so a server that stops responding leaves rotation without waiting for a readiness probe to fail.
Cookie-based session persistence in NGINX Open Source
What’s new: Gateway API’s sessionPersistence on HTTPRoute and GRPCRoute rules no longer needs F5 NGINX Plus. NGINX Gateway Fabric now translates it into a sticky cookie directive on NGINX Open Source too (nginx/nginx-gateway-fabric#6028). The sticky directive has been part of NGINX Open Source since 1.29.6. sessionPersistence is an experimental Gateway API field, so it needs experimental features turned on. The HTTPRoute sessionPersistence API is deprecated and is expected to be replaced by a new API on the Backend resource in a future release. ip_hash on an UpstreamSettingsPolicy remains available for IP-based affinity.
Why it matters: A cookie keeps a user on the same backend even when their IP address changes or many users share one address, which IP-based affinity can’t do. Until this release, NGINX Gateway Fabric offered it only with NGINX Plus.
Prometheus ServiceMonitors for the control plane and the data plane
What’s new: If you run the Prometheus Operator, NGINX Gateway Fabric can create ServiceMonitors for its own metrics. For the control plane, turn on nginxGateway.metrics.serviceMonitor.enable in the Helm chart, with optional labels, annotations, a scrape interval, and a tlsConfig for secure metrics. For the data plane, set serviceMonitor on the NginxProxy’s kubernetes.deployment or kubernetes.daemonSet, or nginx.serviceMonitor in the chart, and install the Prometheus Operator’s ServiceMonitor custom resource definition (CRD) before the control plane starts. The control plane then creates, for each Gateway, a ServiceMonitor and a headless ClusterIP Service that exposes only the NGINX metrics port. The ServiceMonitor scrapes through that Service, so metrics stay inside the cluster whatever type the Gateway’s traffic Service is (ServiceMonitors nginx/nginx-gateway-fabric#5776, metrics Service nginx/nginx-gateway-fabric#6054).
Why it matters: Scraping a Gateway’s data plane used to mean writing a ServiceMonitor by hand for each one and keeping its selectors in step with what NGINX Gateway Fabric provisions. Now the control plane creates it with the Gateway, so metrics collection keeps pace as you add Gateways, and the metrics endpoint is never published on the Gateway’s LoadBalancer or NodePort Service.
Access logs to a file or a syslog server
What’s new: The NginxProxy’s spec.logging.accessLog takes a destination. Set type: syslog with a syslog.server in host:port form to send access logs to a syslog collector, or type: file with a file.path to write them to a file. Without a destination, access logs go to /dev/stdout as before. A destination takes effect only when an access log format is also set: a custom format, or errorLogFormat: json for the built-in JSON format (nginx/nginx-gateway-fabric#5765). To use a file destination, mount a separate volume for the log file through the NginxProxy’s volumes and volumeMounts.
Why it matters: Access logs can go where your log pipeline already looks — the syslog collector you run, or a file a log shipper picks up — rather than only to the container’s stdout.
Smoother rolling upgrades
What’s new: When a data plane pod connects, the control plane now compares the image that pod is running with the version it expects, and rejects the connection on a mismatch. In the middle of a rolling upgrade, an old pod is no longer sent configuration built for the new version. The check also works in host network mode, where the pod is identified from its bound service account token (nginx/nginx-gateway-fabric#5927).
Why it matters: A rolling upgrade runs old and new data plane pods side by side for a while. Configuring only the pods that match the control plane’s version keeps old pods from applying configuration they weren’t built for, which caused intermittent failures in upgrades to NGINX Gateway Fabric 2.7.0.
Leaner watches on the Kubernetes API
What’s new: Two changes cut what the control plane asks of the Kubernetes API server:
- Secrets can be filtered by label: The new
--watch-secret-label-selectorcontrol plane flag limits the Secrets the control plane watches and caches to the ones carrying a label you choose, such asgateway.nginx.org/watch=true. That includes the Secrets the control plane reads for itself. Before you turn it on, label every Secret your Gateways use, including TLS certificates, the agent TLS Secret, and any NGINX image pull Secrets. Otherwise, provisioning of the NGINX data plane fails. The Helm chart doesn’t yet support this flag, so add it to the control plane Deployment’s arguments and roll out the Deployment (nginx/nginx-gateway-fabric#5946). - Watches stay open for as long as the API server allows: A 10-second timeout on the control plane’s Kubernetes client applied to every request, long-running informer watches included, so each watch was canceled and reopened every 10 seconds. That client-wide timeout is gone, and short-lived calls keep their own deadlines. Thanks to community contributor akshar27 for this fix (nginx/nginx-gateway-fabric#5814).
Why it matters: With the selector set, a control plane on a cluster full of unrelated Secrets caches only the ones you’ve labeled, so its memory follows your Gateway configuration rather than the size of the cluster. Each informer also keeps one watch open instead of opening a new one every 10 seconds, which takes load off the API server.
Verified TLS to the inference endpoint picker
What’s new: For Gateway API Inference Extension users, NGINX Gateway Fabric now honors a BackendTLSPolicy attached to the endpoint picker Service that an InferencePool references, and checks the picker’s certificate against the trusted certificate authority that policy specifies. The Helm value nginxGateway.gwAPIInferenceExtension.endpointPicker.skipVerify now defaults to false. Without a BackendTLSPolicy, the connection stays encrypted but unverified, and the HTTPRoute reports it with a gateway.nginx.org/EndpointPickerTLSVerificationSkipped condition (nginx/nginx-gateway-fabric#5980).
Why it matters: The endpoint picker decides which model server handles each inference request. A BackendTLSPolicy lets NGINX Gateway Fabric check that it’s talking to the picker you deployed; without one, the HTTPRoute’s status says verification is skipped, so the gap is visible rather than silent.
Smaller enhancements
- Data plane PodDisruptionBudgets in the Helm chart:
nginx.podDisruptionBudgettakes the same keys as the control plane’snginxGateway.podDisruptionBudget—enable,minAvailable,maxUnavailable, andunhealthyPodEvictionPolicy— and renders them into the NginxProxy when the data plane runs as a Deployment. NGINX Gateway Fabric 2.7 added the API, but the chart could set a budget only for the control plane.minAvailabledefaults to1; set it to""to usemaxUnavailableinstead. Community contributor owaisaamir built this (nginx/nginx-gateway-fabric#5964).
Stability and bug fixes
- Upstream connections are reused by default: Since NGINX Gateway Fabric 2.7.0 left upstream keepalive to the NGINX default, locations without an explicit
keepAlive.connectionsstill sentConnection: closeto the backend, so every request opened a new upstream connection. They now use the keepaliveConnectionheader unless an UpstreamSettingsPolicy setskeepAlive.connectionsto0(nginx/nginx-gateway-fabric#6010). - Deleted EndpointSlices are removed from upstreams: An EndpointSlice delete event carries no labels, so it couldn’t be traced back to its Service, and NGINX kept the deleted endpoints in the upstream, where they could still receive requests. NGINX Gateway Fabric now records which Service owns each EndpointSlice and rebuilds the configuration when one is deleted (nginx/nginx-gateway-fabric#5968).
- Gateway addresses are set as the Service’s external IPs again: NGINX Gateway Fabric 2.7.0 stopped copying the IP addresses in a Gateway’s
spec.addressesinto the data plane Service’sexternalIPs, so clusters that rely on external IPs refused connections to those addresses. They’re set again, on every Service type. Kubernetes 1.36 deprecates Service external IPs, and NGINX Gateway Fabric supports the field until Kubernetes removes it. Plan your move away from external IPs (nginx/nginx-gateway-fabric#5896). - Data plane pods recover when their init container runs again: After a node or runtime disruption, a data plane pod’s init container could run again while its generated configuration survived. The init container then replaced the main NGINX configuration with a bootstrap version that didn’t load the OpenTelemetry module, or the F5 WAF for NGINX module on NGINX Plus. NGINX couldn’t start on the configuration it kept, and the pod never became ready. The bootstrap configuration now loads each module when it’s enabled. This fix comes from community contributor devaniketh (nginx/nginx-gateway-fabric#5858).
- Upstream servers are listed in a stable order: Resolved endpoints came back in no fixed order, so a rebuild could reorder the
serverlines, change the configuration hash, and trigger a reload that dropped long-lived gRPC and WebSocket streams. They’re now sorted by address and port. Developed by community contributor devaniketh (nginx/nginx-gateway-fabric#5805). - Gateway status lists every routable address: With
spec.addressesset, IP addresses assigned to the Gateway’s Service were left out of the Gateway’s status. Status now carries the static addresses along with the Service’s load balancer IP addresses and hostnames (nginx/nginx-gateway-fabric#6019). - Client certificate validation works with an
OpaqueCA Secret: When a Gateway’s frontend TLS validation referenced anOpaqueSecret holding the CA certificate underca.crt, NGINX Gateway Fabric never wrote the CA bundle, so NGINX failed to reload and rolled the configuration back. It now creates the bundle (nginx/nginx-gateway-fabric#5913). - An empty
caCertificateRefslist no longer crashes the control plane: A BackendTLSPolicy withcaCertificateRefs: []andwellKnownCACertificatesset passed validation, then crashed the control plane on an out-of-range index lookup. An empty list is now treated like an absent one. Thanks to community contributor somaz94 for this fix (nginx/nginx-gateway-fabric#5953). - Inference requests reach the endpoint picker with their path: NGINX Gateway Fabric didn’t forward the request path to the Gateway API Inference Extension endpoint picker, so the picker could choose the wrong parser and fail the request with a
500(nginx/nginx-gateway-fabric#5995). - Every endpoint picker response header is passed on: NGINX Gateway Fabric forwarded only
X-Gateway-Destination-Endpointfrom the endpoint picker’s response and dropped the rest, which broke integrations such as NVIDIA Dynamo that send extra headers to the model servers. It now passes every header except hop-by-hop and framing headers into NGINX variables. A SnippetsFilter then sends the headers you need to the backend withproxy_set_header(nginx/nginx-gateway-fabric#6065). - Stale status is cleared from resources the controller stops handling: When a route loses its parentRef, or a policy, filter, or ListenerSet drops out of the configuration while staying in the cluster, NGINX Gateway Fabric now removes the status it wrote. Status written by other controllers is left alone (nginx/nginx-gateway-fabric#5978).
- Service patches in the Helm chart reach the NginxProxy: A
patcheslist undernginx.servicein the Helm values wasn’t rendered into the NginxProxy, so the patches never reached the data plane Service. The chart now renders them. Community contributor devaniketh built this (nginx/nginx-gateway-fabric#5857). - [Enterprise only]
waf-config-mgrstarts without error noise: With F5 WAF for NGINX enabled, the sidecar loggedError with config_set.jsoncontinuously until the first WAFPolicy loaded. The NGINX container now creates an emptyconfig_set.jsonat startup, as it already did forpolicy_path.map. Community contributor divyanshu1810 wrote this fix (nginx/nginx-gateway-fabric#5863).
Under the hood
- Logging follows one set of guidelines, enforced by a linter: Log messages read consistently across the codebase, loggers are injected the way the project’s logging guidelines describe, and a
logchecklint step keeps it that way (messages nginx/nginx-gateway-fabric#5826, injection nginx/nginx-gateway-fabric#5777, linting nginx/nginx-gateway-fabric#5993). - Panic logs are flushed before the process exits: The control plane flushes its logger when it recovers from a panic in its event loops, status updates, provisioner, or gRPC handlers, so the log lines that explain a crash aren’t lost with the process (nginx/nginx-gateway-fabric#5810).
- A simpler change processor: Event batches go straight to
Process(), which applies upserts and deletes before deciding whether to rebuild the graph (nginx/nginx-gateway-fabric#5846). - Route attachment is tracked per listener: A parentRef that attaches to several listeners keeps a hostname intersection and a port for each one, and section names are expanded once, when the graph is built (nginx/nginx-gateway-fabric#5914).
- Policy methods are generated:
make generate-policieswrites the boilerplate methods every policy type needs, so adding a policy no longer means copying them by hand (nginx/nginx-gateway-fabric#5729). - The Helm chart has unit tests:
make helm-unit-testruns helm-unittest suites over every chart template, covering default, conditional, and negative rendering (nginx/nginx-gateway-fabric#5920). - A duplicate BackendTLSPolicy check is gone: The controller no longer re-checks that
caCertificateRefsandwellKnownCACertificatesaren’t both set, which the Gateway API CRD already rejects at admission. Checks the CRD doesn’t fully cover, such as the hostname, stay. This change comes from community contributor somaz94 (nginx/nginx-gateway-fabric#5951).
Upgrade notes
nginx.org/htpasswdSecrets are no longer supported: The type was deprecated in NGINX Gateway Fabric 2.5. Basic authentication now reads onlyOpaqueSecrets with the credentials under theauthkey, so convert anynginx.org/htpasswdSecret an AuthenticationFilter references before you upgrade. Developed by community contributor devaniketh (nginx/nginx-gateway-fabric#5808).loadBalancerClassis left unset unless you set it: NGINX Gateway Fabric 2.7 set the data plane Service’sloadBalancerClassto its own controller name whenever a Gateway’sspec.addressesheld an IP address, which kept cloud provider controllers and MetalLB from managing the Service. It now leaves the field unset unless you configure one. To have NGINX Gateway Fabric manage the load balancer, setloadBalancerClasstogateway.nginx.org/nginx-gateway-controllerin the NginxProxy’sspec.kubernetes.service, or innginx.service.loadBalancerClassin the Helm chart. The NginxProxy also rejectsloadBalancerClasson any Service type other thanLoadBalancer(nginx/nginx-gateway-fabric#5971).- An existing
loadBalancerClassis kept on upgrade: Kubernetes doesn’t allow a Service’sloadBalancerClassto change once it’s set, so a class already on the Gateway’s Service stays, whether NGINX Gateway Fabric 2.7 set it or another controller did. To hand a Service upgraded from 2.7 to a different load balancer, for example after you remove the Gateway’sspec.addresses, delete the Service so NGINX Gateway Fabric recreates it without a class. The recreated Service gets a new ClusterIP and new NodePorts (nginx/nginx-gateway-fabric#6053).
Community recognition
A big thank-you to the community behind NGINX Gateway Fabric 2.8. Your code, testing, and feedback made this release better.
Community contributions
Five community members built part of NGINX Gateway Fabric 2.8: six bug fixes, data plane PodDisruptionBudgets in the Helm chart, the removal of a deprecated Secret type, and a validation cleanup, each credited above next to the change it made. Thank you, akshar27, devaniketh, divyanshu1810, owaisaamir, and somaz94, for helping build this release!
Community inspired development work
We’re also grateful to three contributors whose pull requests didn’t merge but tackled problems this release solves another way. icelynjennings‘s pull request and sanmeshkakade‘s pull request each proposed ServiceMonitor support, which this release delivers in Prometheus ServiceMonitors for the control plane and the data plane. AruneshDwivedi‘s pull request tackled deleted EndpointSlices whose endpoints stayed in NGINX’s upstreams, which the deleted EndpointSlices fix in Stability and bug fixes addresses. We appreciate the effort.
Help shape NGINX Gateway Fabric 2.9
Work on NGINX Gateway Fabric 2.9 is already underway in the open. Pick up an open issue, send a fix for a bug you’ve hit, or propose a feature you’d like to build. The contributing guide walks you through it, from opening a discussion to sending a pull request.
Also in this release: Features that need an F5 NGINX Enterprise solution
These changes need NGINX Plus, F5 NGINX One Console, or F5 WAF for NGINX. For more information, check the F5 DevCentral release article.
- Active health checks:
healthCheck.activeon UpstreamSettingsPolicy probes each upstream server on a schedule, withinterval,jitter,fails,passes,path,port, amatchon the response status, gRPC health checking,mandatorychecks for new servers,persistenthealth state across reloads, keepalive, timeouts, and request headers. A BackendTLSPolicy attached to the Service applies to its health check requests too. On NGINX Open Source, a policy that setsactiveis rejected (nginx/nginx-gateway-fabric#6000). - The Secret label selector covers commercial Secrets too: With
--watch-secret-label-selectorset, also label the NGINX Plus license (JWT), CA, and client SSL Secrets, the NGINX One Console dataplane key Secret, and any F5 Policy Lifecycle Manager storage Secrets for F5 WAF for NGINX. Otherwise, NGINX Plus usage reporting stops working without a clear error (nginx/nginx-gateway-fabric#5946). - A larger default upstream zone for NGINX Plus: Upstream zones default to
2minstead of1mon NGINX Plus, making room for the extra upstream data that NGINX Plus API version 9 carries (nginx/nginx-gateway-fabric#5859). - NGINX Plus usage reporting works with custom CA and client certificates: With
caSecretNameorclientSSLSecretNameset for usage reporting, NGINX pods crash-looped at startup on a read-only file system error. The certificates are now copied into a writable volume, so NGINX starts and NGINX Agent can still rotate them (nginx/nginx-gateway-fabric#5877). - A shared WAF security log bundle reaches every Gateway: When WAFPolicies attached to different Gateways referenced the same security log bundle, only the Gateway whose policy fetched it first received it, and NGINX on the others failed to start. Every Gateway that references the bundle now gets it (nginx/nginx-gateway-fabric#5895).
- A quieter
waf-config-mgrstartup: The F5 WAF for NGINX fix from community contributor divyanshu1810 is in Stability and bug fixes above (nginx/nginx-gateway-fabric#5863).
Getting started with NGINX Gateway Fabric 2.8
NGINX Gateway Fabric 2.8 is available now as a container image and a Helm chart on GitHub Container Registry (ghcr.io).
- Release notes and changes: Read the full changelog.
- Install and upgrade: Follow the NGINX Gateway Fabric installation guide.
- Documentation: Visit the NGINX Gateway Fabric documentation.
- Moving Ingress resources to Gateway API? Convert them with ingress2gateway, which has providers for NGINX Ingress Controller, Ingress-NGINX, and more.
- Interested in NGINX’s open source Kubernetes ecosystem? Start at kubernetes.nginx.org.
Stay tuned to the NGINX Blog for more release updates and technical deep dives.
Compatibility
NGINX Gateway Fabric 2.8 is compatible with these Kubernetes versions:
- Kubernetes: 1.32 or later
- Gateway API: 1.6.3
- Gateway API Inference Extension: 1.6.2
Keeping NGINX Gateway Fabric up to date
NGINX Gateway Fabric releases include important fixes and ongoing improvements. We encourage users to stay current with the latest version. NGINX Gateway Fabric 2.8 fixes two security issues: the configuration injection in AuthenticationFilter in CVE-2026-91184, and the NGINX Agent connection spoofing in CVE-2026-96615. Each CVE links to its official F5 advisory. NGINX Gateway Fabric 2.8 also ships NGINX Open Source 1.31.7. Since NGINX Gateway Fabric 2.7.0, which shipped NGINX Open Source 1.31.4, that brings fixes for six NGINX CVEs: five in F5’s security notification, and CVE-2026-90439.
To stay informed, subscribe to F5 notifications and select the relevant advisory updates.


