diff --git a/content/ngf/overview/gateway-api-compatibility.md b/content/ngf/overview/gateway-api-compatibility.md
index bf82faf37..33e2dd260 100644
--- a/content/ngf/overview/gateway-api-compatibility.md
+++ b/content/ngf/overview/gateway-api-compatibility.md
@@ -176,7 +176,7 @@ See the [controller]({{< ref "/ngf/reference/cli-help.md#controller">}}) command
- `name`: Not supported.
- `timeouts`: Not supported.
- `retry`: Not supported.
- - `sessionPersistence`: Not supported.
+ - `sessionPersistence`: Supported (NGINX Plus).
- `status`
- `parents`
- `parentRef`: Supported.
@@ -226,7 +226,7 @@ See the [controller]({{< ref "/ngf/reference/cli-help.md#controller">}}) command
- `extensionRef`: Supported for SnippetsFilters.
- `backendRefs`: Partially supported. Backend ref `filters` are not supported.
- `name`: Not supported.
- - `sessionPersistence`: Not supported.
+ - `sessionPersistence`: Supported (NGINX Plus).
- `status`
- `parents`
- `parentRef`: Supported.
diff --git a/content/ngf/overview/nginx-plus.md b/content/ngf/overview/nginx-plus.md
index 923947786..e8e049b76 100644
--- a/content/ngf/overview/nginx-plus.md
+++ b/content/ngf/overview/nginx-plus.md
@@ -13,4 +13,6 @@ NGINX Gateway Fabric can use NGINX Open Source or NGINX Plus as its data plane.
- **Robust metrics**: A plethora of [additional Prometheus metrics]({{< ref "/ngf/monitoring/prometheus.md" >}}) are available.
- **Live activity monitoring**: The [NGINX Plus dashboard]({{< ref "/ngf/monitoring/dashboard.md" >}}) shows real-time metrics and information about your server infrastructure.
- **Dynamic upstream configuration**: NGINX Plus can dynamically reconfigure upstream servers when applications in Kubernetes scale up and down, preventing the need for an NGINX reload.
+- **Session persistence**: NGINX Plus provides support for cookie-based session persistence, allowing client requests to be consistently routed to the same upstream pod.
+- **Load balancing methods**: NGINX Plus provides advanced, latency-aware load balancing methods (such as `least_time` and `least_time last_byte inflight`) that route traffic based on time to first byte or full response, optionally factoring in in-flight requests for smarter upstream selection.
- **Support**: With an NGINX Plus license, you can take advantage of full [support](https://my.f5.com/manage/s/article/K000140156/) from NGINX, Inc.
diff --git a/content/ngf/reference/api.md b/content/ngf/reference/api.md
index 407093f42..565577009 100644
--- a/content/ngf/reference/api.md
+++ b/content/ngf/reference/api.md
@@ -4216,4 +4216,4 @@ Examples of invalid names: some-$value, quoted-“value”-name, unescap
Generated with gen-crd-api-reference-docs
-
+
\ No newline at end of file
diff --git a/content/ngf/traffic-management/session-persistence.md b/content/ngf/traffic-management/session-persistence.md
new file mode 100644
index 000000000..aaeb00aa3
--- /dev/null
+++ b/content/ngf/traffic-management/session-persistence.md
@@ -0,0 +1,558 @@
+---
+title: Session Persistence
+weight: 1000
+toc: true
+nd-content-type: how-to
+nd-product: FABRIC
+nd-docs:
+---
+
+Learn how to configure session persistence using NGINX Gateway Fabric.
+
+## Overview
+
+In this guide, you’ll learn how to configure session persistence for your application. Session persistence ensures that multiple requests from the same client are consistently routed to the same backend Pod. This is useful when your application maintains in-memory state (for example, shopping carts or user sessions). NGINX Gateway Fabric supports configuring session persistence via `UpstreamSettingsPolicy` resource or directly on `HTTPRoute` and `GRPCRoute` resources. For NGINX OSS users, using the `ip_hash` load-balancing method provides basic session affinity by routing requests from the same client IP to the same backend Pod. For NGINX Plus users, cookie-based session persistence can be configured using the `sessionPersistence` field in a Route.
+In this guide, you will deploy three applications:
+
+- An application configured with `ip_hash` load-balancing method.
+- An application configured with cookie–based session persistence (if you have access to NGINX Plus).
+- A regular application with default load-balancing.
+
+These applications will showcase the benefits of session persistence for stateful workloads.
+
+The NGINX directives discussed in this guide are:
+
+- [`ip_hash`](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#ip_hash)
+- [`sticky cookie`](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#sticky)
+
+## Note
+
+{{< call-out "important" >}}`SessionPersistence` is only available for [NGINX Plus]({{< ref "/ngf/install/nginx-plus.md" >}}) users, with alternatives provided for NGINX OSS users. It is a Gateway API field from the experimental release channel and is subject to change. {{< /call-out >}}
+
+{{< include "/ngf/installation/install-gateway-api-experimental-features.md" >}}
+
+## Before you begin
+
+- [Install]({{< ref "/ngf/install/" >}}) NGINX Gateway Fabric with experimental features enabled.
+
+## Setup
+
+Create the `coffee`, `tea` and `latte` applications:
+
+```yaml
+kubectl apply -f - <
+pod/coffee-5b9c74f9d9-7gfwn 1/1 Running 0 3h19m 10.244.0.94 kind-control-plane
+pod/latte-d5f64f67f-9t2j5 1/1 Running 0 3h19m 10.244.0.96 kind-control-plane
+pod/latte-d5f64f67f-drwc6 1/1 Running 0 3h19m 10.244.0.98 kind-control-plane
+pod/tea-859766c68c-cnb8n 1/1 Running 0 3h19m 10.244.0.93 kind-control-plane
+pod/tea-859766c68c-kttkb 1/1 Running 0 3h19m 10.244.0.97 kind-control-plane
+
+NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
+service/coffee ClusterIP 10.96.169.1 80/TCP 3h19m app=coffee
+service/latte ClusterIP 10.96.42.39 80/TCP 3h19m app=latte
+service/tea ClusterIP 10.96.81.103 80/TCP 3h19m app=tea
+```
+
+Create a Gateway:
+
+```yaml
+kubectl apply -f - <
+```
+
+Lookup the name of the NGINX pod and save into shell variable:
+
+```text
+NGINX_POD_NAME=
+```
+
+{{< call-out "note" >}}In a production environment, you should have a DNS record for the external IP address that is exposed, and it should refer to the hostname that the gateway will forward for.{{< /call-out >}}
+
+## Session Persistence Methods
+
+### Session Persistence with NGINX OSS
+
+In this section, you’ll configure a basic `coffee` HTTPRoute that routes traffic to the `coffee` Service. You’ll then attach an `UpstreamSettingsPolicy` to change the load-balancing method for that upstream to showcase session affinity behavior. NGINX hashes the client IP to select an upstream server, so requests from the same IP are routed to the same upstream as long as it is available. Session affinity quality with `ip_hash` depends on NGINX seeing the real client IP. In environments with external load balancers or proxies, operators must ensure appropriate `real_ip_header/set_real_ip_from` configuration so that `$remote_addr` reflects the end-user address otherwise, stickiness will be determined by the address of the front-end proxy rather than the actual client.
+
+To create an HTTPRoute for the `coffee` service, copy and paste the following into your terminal:
+
+```yaml
+kubectl apply -f - < $NGINX_POD_NAME -- nginx -T
+```
+
+You should see the `ip_hash` directive on the `coffee` upstream:
+
+```text
+upstream default_coffee_80 {
+ ip_hash;
+ zone default_coffee_80 1m;
+ state /var/lib/nginx/state/default_coffee_80.conf;
+}
+```
+
+In this example, the `coffee` Service currently has two backend Pods with IPs `10.244.0.95` and `10.244.0.94`. We’ll send five requests to the `/coffee` endpoint and observe that the responses consistently come from the same backend Pod, demonstrating session affinity.
+
+```shell
+for i in $(seq 5); do
+ echo "Request #$i"
+ curl -s -H "Host: cafe.example.com" \
+ http://localhost:8080/coffee \
+ | grep -E 'Server (address|name)'
+ echo
+done
+```
+
+You will observe that all responses are served by the Pod `coffee-5b9c74f9d9-7gfwn` with IP `10.244.0.94:8080`:
+
+```text
+Request #1
+Server address: 10.244.0.94:8080
+Server name: coffee-5b9c74f9d9-7gfwn
+
+Request #2
+Server address: 10.244.0.94:8080
+Server name: coffee-5b9c74f9d9-7gfwn
+
+Request #3
+Server address: 10.244.0.94:8080
+Server name: coffee-5b9c74f9d9-7gfwn
+
+Request #4
+Server address: 10.244.0.94:8080
+Server name: coffee-5b9c74f9d9-7gfwn
+
+Request #5
+Server address: 10.244.0.94:8080
+Server name: coffee-5b9c74f9d9-7gfwn
+```
+
+### Session Persistence with NGINX Plus
+
+You can configure session persistence by specifying the `sessionPersistence` field on an `HTTPRouteRule` or `GRPCRouteRule`. This configuration is translated to the `sticky cookie` directive on the NGINX data plane. In this guide, you’ll create a `tea` HTTPRoute with `sessionPersistence` configured at the rule level and then verify how traffic behaves when the route has multiple backend Pods.
+
+```yaml
+kubectl apply -f - < $NGINX_POD_NAME -- nginx -T
+```
+
+```text
+upstream default_tea_80_tea_default_0 {
+ random two least_conn;
+ zone default_tea_80_tea_default_0 1m;
+ sticky cookie cookie-tea expires=24h path=/tea;
+ state /var/lib/nginx/state/default_tea_80.conf;
+}
+```
+
+In this example, the `tea` `Service` has two backend Pods with IPs `10.244.0.93` and `10.244.0.97`. We’ll send five requests to the `/tea` endpoint and observe that all responses are served by the same backend Pod, demonstrating cookie-based session persistence.
+
+First, send a request to `/tea` and store the session cookie:
+
+```shell
+curl -v -c /tmp/tea-cookies.txt \
+ -H "Host: cafe.example.com" \
+ http://localhost:8080/tea
+```
+
+You’ll see a cookie being set, for example:
+
+```text
+* Added cookie cookie-tea="2878e97a4c7a8406b791aa0bd0b2f145" for domain cafe.example.com, path /tea, expire 1765417195
+< Set-Cookie: cookie-tea=2878e97a4c7a8406b791aa0bd0b2f145; expires=Thu, 11-Dec-25 01:39:55 GMT; max-age=86400; path=/tea
+```
+
+Next, send five requests using the stored cookie:
+
+```shell
+for i in $(seq 5); do
+ echo "Request #$i"
+ curl -s -b /tmp/tea-cookies.txt \
+ -H "Host: cafe.example.com" \
+ http://localhost:8080/tea \
+ | grep -E 'Server (address|name)'
+ echo
+done
+```
+
+All responses are served by the same backend Pod, `tea-859766c68c-cnb8n` with IP `10.244.0.93:8080`, confirming session persistence:
+
+```text
+Request #1
+Server address: 10.244.0.93:8080
+Server name: tea-859766c68c-cnb8n
+
+Request #2
+Server address: 10.244.0.93:8080
+Server name: tea-859766c68c-cnb8n
+
+Request #3
+Server address: 10.244.0.93:8080
+Server name: tea-859766c68c-cnb8n
+
+Request #4
+Server address: 10.244.0.93:8080
+Server name: tea-859766c68c-cnb8n
+
+Request #5
+Server address: 10.244.0.93:8080
+Server name: tea-859766c68c-cnb8n
+```
+
+### Regular Application
+
+We’ll create routing rules for `latte` application without any session affinity or persistence settings and then verify how the traffic behaves.
+
+Let’s create the `latte` HTTPRoute:
+
+```yaml
+kubectl apply -f - < $NGINX_POD_NAME -- nginx -T
+```
+
+```text
+upstream default_latte_80 {
+ random two least_conn;
+ zone default_latte_80 1m;
+ state /var/lib/nginx/state/default_latte_80.conf;
+}
+```
+
+In this example, the `latte` Service currently has two backend Pods with IPs `10.244.0.96` and `10.244.0.98`. We’ll send five requests to the `/latte` endpoint and observe which backend Pod serves each response to understand how a regular backend behaves without any session affinity or persistence configured.
+
+```shell
+for i in $(seq 5); do
+ echo "Request #$i"
+ curl -s -H "Host: cafe.example.com" \
+ http://localhost:8080/latte \
+ | grep -E 'Server (address|name)'
+ echo
+done
+```
+
+You will see responses coming from both backend Pods, for example:
+
+```text
+Request #1
+Server address: 10.244.0.98:8080
+Server name: latte-d5f64f67f-drwc6
+
+Request #2
+Server address: 10.244.0.96:8080
+Server name: latte-d5f64f67f-9t2j5
+
+Request #3
+Server address: 10.244.0.98:8080
+Server name: latte-d5f64f67f-drwc6
+
+Request #4
+Server address: 10.244.0.98:8080
+Server name: latte-d5f64f67f-drwc6
+
+Request #5
+Server address: 10.244.0.96:8080
+Server name: latte-d5f64f67f-9t2j5
+```
+
+Because there is no session persistence configured for `latte`, traffic is distributed across both backend Pods according to the default load-balancing method, and requests from the same client are not guaranteed to hit the same Pod.
+
+## Further reading
+
+- [Session Persistence](https://gateway-api.sigs.k8s.io/reference/spec/?h=sessionpersistence#sessionpersistence).
+- [API reference]({{< ref "/ngf/reference/api.md" >}}): all configuration fields for the `UpstreamSettingsPolicy` API.
\ No newline at end of file
diff --git a/content/ngf/traffic-management/upstream-settings.md b/content/ngf/traffic-management/upstream-settings.md
index b27fe738d..f7772ad62 100644
--- a/content/ngf/traffic-management/upstream-settings.md
+++ b/content/ngf/traffic-management/upstream-settings.md
@@ -19,14 +19,21 @@ The settings in `UpstreamSettingsPolicy` correspond to the following NGINX direc
- [`keepalive`]()
- [`keepalive_requests`]()
- [`keepalive_time`]()
-- [`keepalive_timeout`]()
+- [`keepalive_timeout`](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive_timeout)
+- [`random`](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#random)
+- [`least_conn`](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#least_conn)
+- [`least_time`](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#least_time)
+- [`upstream`](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream)
+- [`ip_hash`](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#ip_hash)
+- [`hash`](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#hash)
+- [`variables`](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#variables)
`UpstreamSettingsPolicy` is a [Direct Policy Attachment](https://gateway-api.sigs.k8s.io/reference/policy-attachment/) that can be applied to one or more services in the same namespace as the policy.
`UpstreamSettingsPolicies` can only be applied to HTTP or gRPC services, in other words, services that are referenced by an HTTPRoute or GRPCRoute.
See the [custom policies]({{< ref "/ngf/overview/custom-policies.md" >}}) document for more information on policies.
-This guide will show you how to use the `UpstreamSettingsPolicy` API to configure the upstream zone size and keepalives for your applications.
+This guide will show you how to use the `UpstreamSettingsPolicy` API to configure the load balancing method, upstream zone size and keepalives for your applications.
For all the possible configuration options for `UpstreamSettingsPolicy`, see the [API reference]({{< ref "/ngf/reference/api.md" >}}).
@@ -237,6 +244,100 @@ Server name: tea-76c7c85bbd-cf8nz
---
+## Configure load balancing methods
+
+You can use `UpstreamSettingsPolicy` to configure the load balancing method for the `coffee` and `tea` applications. In this example, the `coffee` service uses the `random two least_time=header` method, and the `tea` service uses the `hash consistent` method with `$upstream_addr` as the hash key.
+
+{{< call-out "note" >}} You need to specify an NGINX [variable](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#variables) as `hashMethodKey` when using load balancing methods `hash` and `hash consistent` .{{< /call-out >}}
+
+Create the following `UpstreamSettingsPolicy` resources:
+
+```yaml
+kubectl apply -f - < $NGINX_POD_NAME -- nginx -T
+```
+
+You should see the `random two least_time=header` directive on the `coffee` upstreams and `hash $upstream_addr consistent` in the `tea` upstream:
+
+```text
+upstream default_coffee_80 {
+ random two least_time=header;
+ zone default_coffee_80 1m;
+ state /var/lib/nginx/state/default_coffee_80.conf;
+}
+
+upstream default_tea_80 {
+ hash $upstream_addr consistent;
+ zone default_tea_80 1m;
+ state /var/lib/nginx/state/default_tea_80.conf;
+}
+```
+
+{{< call-out "note" >}}
+NGINX Open Source supports the following load-balancing methods: `round_robin`, `least_conn`, `ip_hash`, `hash`, `hash consistent`, `random`, `random two`, and `random two least_conn`.
+NGINX Plus supports all of the methods available in NGINX Open Source, and adds the following methods: `random two least_time=header`, `random two least_time=last_byte`, `least_time header`, `least_time last_byte`, `least_time header inflight`, and `least_time last_byte inflight`.
+{{< /call-out >}}
+
## Configure upstream zone size
To set the upstream zone size to 1 megabyte for both the `coffee` and `tea` services, create the following `UpstreamSettingsPolicy`: