# Securing backend traffic using mutual TLS Type of document: How-to guide Product: NGINX Gateway Fabric --- Learn how to encrypt HTTP traffic between NGINX Gateway Fabric and your backend pods using mutual TLS between Gateway and Backend applications. ## Overview In this guide, you configure the TLS connection from the Gateway to a secure application using [BackendTLSPolicy](https://gateway-api.sigs.k8s.io/api-types/backendtlspolicy/) together with the Gateway’s backend TLS settings. The examples show how to validate the backend’s certificate and present a client certificate, so that traffic between the Gateway and the application is protected with mutual TLS. The intended use case is when a service or backend owner manages their own HTTPS configuration and certificates, and NGINX Gateway Fabric needs to know how to connect securely to this backend over HTTPS while also proving its own identity with a client certificate. This ensures that all traffic between the Gateway and the application is secured. The following diagram shows how the mTLS handshake takes place between NGINX Gateway Fabric and the secure-app application: ```mermaid sequenceDiagram participant client as Client participant gw as NGINX Gateway Fabric participant app as secure-app Application client->>gw: Request gw->>app: HTTPS request gw->>app: start TLS handshake app->>gw: request client certificate gw->>app: present client certificate from Secret: gateway-presents-this-cert-for-validation app->>app: validate client certificate using ca.crt in Secret: app-tls-secret app->>gw: present backend certificate gw->>gw: validate backend certificate using BackendTLSPolicy and ca.crt in Secret: backend-cert app->>gw: complete TLS handshake app-->>gw: HTTPS response gw-->>client: Response ``` ## Before you begin - [Install](/ngf/install/) NGINX Gateway Fabric. ## Set up Install cert-manager onto the cluster using Helm with Gateway API features enabled. - Add the Helm repository. ```shell helm repo add jetstack https://charts.jetstack.io helm repo update ``` - Install cert-manager, and enable the GatewayAPI feature gate: ```shell helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ --set config.apiVersion="controller.config.cert-manager.io/v1alpha1" \ --set config.kind="ControllerConfiguration" \ --set config.enableGatewayAPI=true \ --set crds.enabled=true ``` Create a self-signed `ClusterIssuer`, a CA `Certificate`, and a CA-backed `ClusterIssuer`. cert-manager uses the resulting `local-ca-issuer` to sign certificates in any namespace: ```yaml kubectl apply -f - < 8443/TCP 9s ``` ## Configure routing rules First, create the Gateway resource with an HTTP listener: ```yaml kubectl apply -f - < ``` **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. --- ## Send traffic without mutual TLS configured Using the external IP address and port for the NGINX Service, send traffic to the secure-app application. To show what happens before we configure backend TLS and have the Gateway present its client certificate for verification, send a request now and observe how the connection to the application fails with a bad request error. **Note:** If you have a DNS record allocated for `secure-app.example.com`, you can send the request directly to that hostname, without needing to resolve. ```shell curl --resolve secure-app.example.com:$GW_PORT:$GW_IP http://secure-app.example.com:$GW_PORT/ ``` ```text 400 The plain HTTP request was sent to HTTPS port

400 Bad Request

The plain HTTP request was sent to HTTPS port

nginx/1.29.2
``` We can see a status 400 Bad Request message from NGINX. --- ## Configure TLS for Gateway and Backend applications To create a Secret named `gateway-presents-this-cert-for-validation` signed by the local CA that Gateway presents to verify its identity, copy and paste the following command: ```yaml kubectl apply -f - < Annotations: API Version: gateway.networking.k8s.io/v1 Kind: BackendTLSPolicy Metadata: Creation Timestamp: 2025-11-13T23:28:36Z Generation: 1 Resource Version: 1288 UID: d7e3f026-afe3-44d1-aed5-c168e954b52f Spec: Target Refs: Group: Kind: Service Name: secure-app Validation: Ca Certificate Refs: Group: Kind: Secret Name: backend-cert Hostname: secure-app.example.com Status: Ancestors: Ancestor Ref: Group: gateway.networking.k8s.io Kind: Gateway Name: gateway Namespace: default Conditions: Last Transition Time: 2025-11-13T23:28:37Z Message: All CACertificateRefs are resolved Observed Generation: 1 Reason: ResolvedRefs Status: True Type: ResolvedRefs Last Transition Time: 2025-11-13T23:28:37Z Message: The Policy is accepted Observed Generation: 1 Reason: Accepted Status: True Type: Accepted Controller Name: gateway.nginx.org/nginx-gateway-controller Events: ``` --- ## Send traffic with backend TLS configuration Now send traffic again: ```shell curl --resolve secure-app.example.com:$GW_PORT:$GW_IP http://secure-app.example.com:$GW_PORT/ ``` ```text hello from pod secure-app ``` To verify that the backend validated the gateway’s client certificate, inspect the logs of the `secure-app` pod and check the reported client subject: ```shell POD_NAME=$(kubectl get pod -l app=secure-app -o jsonpath='{.items[0].metadata.name}') kubectl logs "$POD_NAME" ``` ```text 10.244.0.145 ssl_client_verify=SUCCESS ssl_client_subject=CN=gateway ``` --- ## See also To learn more about configuring backend TLS termination using the Gateway API, see the following resources: - [Backend TLS Policy](https://gateway-api.sigs.k8s.io/api-types/backendtlspolicy/) - [Backend TLS Policy GEP](https://gateway-api.sigs.k8s.io/geps/gep-1897/) - [Gateway Backend TLS](https://gateway-api.sigs.k8s.io/reference/spec/#gatewaybackendtls)