Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion content/ngf/traffic-management/listener-sets.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ Request ID: 7dbd29ec0c783475d50ed3b563b0a8a6

## See Also

To set up HTTPS Termination or TLS passthrough on a listener from a `ListenerSet`, configure the listener on the `ListenerSet` as you would on a Gateway. Follow our [HTTPS Termination]({{< ref "ngf/traffic-management/https-termination.md" >}}) and [TLS passthrough]({{<ref "ngf/traffic-management/tls-passthrough.md" >}}) guides and copy the Gateway listener's configuration onto a `ListenerSet` to mimic the behavior.
To set up HTTPS Termination, TLS passthrough, or TLSRoute Terminate mode on a listener from a `ListenerSet`, configure the listener on the `ListenerSet` as you would on a Gateway. Follow our [HTTPS Termination]({{< ref "ngf/traffic-management/https-termination.md" >}}) and [TLS routing with TLSRoute]({{<ref "ngf/traffic-management/tls-passthrough.md" >}}) guides and copy the Gateway listener's configuration onto a `ListenerSet` to mimic the behavior.

To learn more about the `ListenerSet` Gateway API, see the following resources:

Expand Down
242 changes: 228 additions & 14 deletions content/ngf/traffic-management/tls-passthrough.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
---
title: Configure TLS passthrough with TLSRoute
title: Configure TLS routing with TLSRoute
weight: 600
toc: true
f5-content-type: how-to
f5-product: FABRIC
f5-docs: DOCS-1850
f5-summary: >
NGINX Gateway Fabric can route encrypted TLS traffic straight to a backend without decrypting it, using a TLSRoute resource and SNI-based routing.
The backend terminates TLS itself with its own certificate, issued in this guide by cert-manager. The Gateway only reads the SNI to pick the right backend.
Use TLS passthrough when the backend needs to handle its own TLS, keep its private key off the Gateway, or serve a non-HTTP protocol over TLS.
NGINX Gateway Fabric supports two TLS modes for TLSRoute: Passthrough and Terminate.
In Passthrough mode, the Gateway forwards encrypted traffic to the backend using SNI-based routing, and the backend terminates TLS with its own certificate.
In Terminate mode, the Gateway holds the certificate and terminates TLS, then forwards plain TCP traffic to the backend.
---

Learn how to use TLSRoutes to forward TLS traffic through NGINX Gateway Fabric.
Learn how to configure TLS routing with [TLSRoute](https://gateway-api.sigs.k8s.io/reference/spec/#tlsroute) using NGINX Gateway Fabric.

## Overview

In this guide, we will show how to configure TLS passthrough for your application, using a [TLSRoute](https://gateway-api.sigs.k8s.io/reference/spec/#tlsroute).
TLSRoute supports two TLS modes:

- **Passthrough**: The Gateway reads the SNI and forwards encrypted TCP traffic to the backend. The backend holds and terminates TLS with its own certificate. Use this mode when the backend needs its own certificate, or when you can't expose the private key to the gateway.
- **Terminate**: The Gateway holds the certificate, terminates TLS, and forwards plain TCP to the backend. Use this mode when the backend shouldn't handle TLS, or when it serves a non-HTTP TCP protocol.

{{< call-out "note" >}}You can add an HTTPS listener on the same port that terminates TLS connections, as long as the hostname doesn't overlap with the TLS listener hostname.{{< /call-out >}}

## Before you begin

- [Install]({{< ref "/ngf/install/" >}}) NGINX Gateway Fabric.

## Set up
Set up cert-manager and a local CA for both examples:

{{< include "ngf/deploy-cert-manager.md" >}}

{{< include "ngf/cert-manager-local-ca.md" >}}

## TLS passthrough

### Set up

Create a `Certificate` for `app.example.com`. cert-manager creates the `app-tls-secret` Secret, which contains `tls.crt`, `tls.key`, and `ca.crt` and is mounted by the `secure-app` Pod:

```yaml
Expand Down Expand Up @@ -139,7 +148,7 @@ NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/secure-app ClusterIP 192.168.194.152 <none> 8443/TCP 12s
```

Create a Gateway. This will create a TLS listener with the hostname `*.example.com` and passthrough TLS mode. Copy and paste this into your terminal.
Create a Gateway with a TLS listener in Passthrough mode. Copy and paste this into your terminal:

```yaml
kubectl apply -f - <<EOF
Expand All @@ -165,11 +174,9 @@ spec:
EOF
```

This Gateway will configure NGINX Gateway Fabric to accept TLS connections on port 443 and route them to the corresponding backend Services without decryption. The routing is done based on the SNI, which allows clients to specify a server name (like example.com) during the SSL handshake.

{{< call-out "note" >}}It is possible to add an HTTPS listener on the same port that terminates TLS connections so long as the hostname does not overlap with the TLS listener hostname.{{< /call-out >}}
This Gateway configures NGINX Gateway Fabric to accept TLS connections on port 443 and forward them to the backend without decryption. The routing uses the SNI, which lets clients specify a server name during the TLS handshake.

After creating the Gateway resource, NGINX Gateway Fabric will provision an NGINX Pod and Service fronting it to route traffic. Verify the gateway is created:
After creating the Gateway resource, NGINX Gateway Fabric provisions an NGINX Pod and Service to route traffic. Verify the Gateway is created:

```shell
kubectl describe gateways.gateway.networking.k8s.io gateway
Expand Down Expand Up @@ -234,7 +241,7 @@ EOF

{{< call-out "note" >}}To route to a Service in a Namespace different from the TLSRoute Namespace, create a [ReferenceGrant](https://gateway-api.sigs.k8s.io/reference/spec/#referencegrant) to permit the cross-namespace reference. {{< /call-out >}}

## Send traffic
### Send traffic

Using the external IP address and port for the NGINX Service, send traffic to the `secure-app` application.

Expand Down Expand Up @@ -287,8 +294,215 @@ hello from pod secure-app-59bbd475b-phgsv

Note that the server certificate used to terminate the TLS connection has the subject common name of `app.example.com`. This is the server certificate that the `secure-app` is configured with and shows that the TLS connection was terminated by the `secure-app`, not NGINX Gateway Fabric.

## TLS terminate

In Terminate mode, NGINX Gateway Fabric holds the TLS certificate, terminates the TLS connection, and forwards plain TCP traffic to the backend. The backend doesn't need a certificate or TLS configuration.

Use TLS terminate mode when:

- Your backend serves a non-HTTP TCP protocol, such as a database or custom binary protocol.
- You want to centralize certificate management at the gateway rather than on each backend.

{{< call-out "note" >}}If your backend serves HTTP traffic and you need HTTP-level routing — such as path matching or header manipulation — use an HTTPS listener with an HTTPRoute instead. See [Configure HTTPS termination]({{< ref "/ngf/traffic-management/https-termination.md" >}}).{{< /call-out >}}

### Set up

Create a `Certificate` for `app.example.com`. cert-manager creates the `gateway-tls-secret` Secret, which the Gateway uses to terminate TLS:

```yaml
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: gateway-cert
namespace: default
spec:
secretName: gateway-tls-secret
issuerRef:
name: local-ca-issuer
kind: ClusterIssuer
commonName: app.example.com
dnsNames:
- app.example.com
EOF
```

Create the `app` application. Because the Gateway terminates TLS, the backend doesn't need a certificate:

```yaml
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 1
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
containers:
- name: app
image: nginxdemos/nginx-hello:plain-text
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: app
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: app
EOF
```

Run the following command to verify the resources were created:

```shell
kubectl get pods,svc
```

The output should include the **app** pod and the **app** Service:

```text
NAME READY STATUS RESTARTS AGE
pod/app-6c8d9c4b5f-xr7tq 1/1 Running 0 10s

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/app ClusterIP 192.168.194.87 <none> 80/TCP 10s
```

Create a Gateway with a TLS listener in Terminate mode. Copy and paste this into your terminal:

```yaml
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway
namespace: default
spec:
gatewayClassName: nginx
listeners:
- name: tls
port: 443
protocol: TLS
hostname: "*.example.com"
allowedRoutes:
namespaces:
from: All
kinds:
- kind: TLSRoute
tls:
mode: Terminate
certificateRefs:
- name: gateway-tls-secret
namespace: default
EOF
```

NGINX Gateway Fabric terminates TLS using the certificate from `gateway-tls-secret` and forwards plain TCP traffic to the backend.

After creating the Gateway resource, NGINX Gateway Fabric provisions an NGINX Pod and Service to route traffic. Verify the Gateway is created:

```shell
kubectl describe gateways.gateway.networking.k8s.io gateway
```

Verify the status is `Accepted`:

```text
Status:
Addresses:
Type: IPAddress
Value: 10.96.36.219
Conditions:
Last Transition Time: 2026-01-09T05:40:37Z
Message: The Gateway is accepted
Observed Generation: 1
Reason: Accepted
Status: True
Type: Accepted
Last Transition Time: 2026-01-09T05:40:37Z
Message: The Gateway is programmed
Observed Generation: 1
Reason: Programmed
Status: True
Type: Programmed
```

Save the public IP address and port of the Gateway into shell variables:

```text
GW_IP=XXX.YYY.ZZZ.III
GW_TLS_PORT=<port number>
```

{{< 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 >}}

Create a TLSRoute that attaches to the Gateway and routes requests to `app.example.com` to the `app` Service:

```yaml
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: TLSRoute
metadata:
name: tls-app-route
namespace: default
spec:
parentRefs:
- name: gateway
namespace: default
hostnames:
- "app.example.com"
rules:
- backendRefs:
- name: app
port: 80
EOF
```

{{< call-out "note" >}}To route to a Service in a Namespace different from the TLSRoute Namespace, create a [ReferenceGrant](https://gateway-api.sigs.k8s.io/reference/spec/#referencegrant) to permit the cross-namespace reference.{{< /call-out >}}

### Send traffic

Using the external IP address and port for the NGINX Service, send traffic to the `app` application.

{{< call-out "note" >}}If you have a DNS record allocated for `app.example.com`, you can send the request directly to that hostname, without needing to resolve.{{< /call-out >}}

Send a request to the `app` Service on the TLS port with the `--insecure` flag. The flag is required because the Gateway uses a certificate signed by a local self-signed CA that curl doesn't trust.

```shell
curl --resolve app.example.com:$GW_TLS_PORT:$GW_IP https://app.example.com:$GW_TLS_PORT --insecure
```

```text
Server address: 10.244.0.9:8080
Server name: app-6f65b8c59b-9lk8j
Date: 14/May/2026:17:06:41 +0000
URI: /
Request ID: dca1e1d0f48b11f50e15007056242349
```

The server certificate subject is `app.example.com`, which matches the certificate in `gateway-tls-secret`. This confirms that NGINX Gateway Fabric terminated the TLS connection, not the backend.
Comment thread
bjee19 marked this conversation as resolved.

## See also

To learn more about TLS routing using the Gateway API, see the following resource:
To learn more about TLS routing using the Gateway API, see the following resources:

- [Gateway API TLS routing](https://gateway-api.sigs.k8s.io/guides/tls-routing/)
- [Configure HTTPS termination]({{< ref "/ngf/traffic-management/https-termination.md" >}})
Loading