Mutual TLS (mTLS)
Set up an mTLS listener on the gateway.
About this guide
In this guide, you learn how to set up an mTLS Gateway. Before the client application and the Gateway establish a connection, both parties must exchange certificates to verify their identities. After a TLS connection is established, the TLS connection is terminated at the Gateway and the unencrypted HTTP traffic is forwarded to the backend destination.
Before you begin
Follow the Get started guide to install Gloo Gateway and deploy the httpbin sample app.
Make sure that you have the OpenSSL version of openssl, not LibreSSL. The openssl version must be at least 1.1.
- Check your
openssl
version. If you see LibreSSL in the output, continue to the next step.openssl version
- Install the OpenSSL version (not LibreSSL). For example, you might use Homebrew.
brew install openssl
- Review the output of the OpenSSL installation for the path of the binary file. You can choose to export the binary to your path, or call the entire path whenever the following steps use an
openssl
command.- For example,
openssl
might be installed along the following path:/usr/local/opt/openssl@3/bin/
- To run commands, you can append the path so that your terminal uses this installed version of OpenSSL, and not the default LibreSSL.
/usr/local/opt/openssl@3/bin/openssl req -new -newkey rsa:4096 -x509 -sha256 -days 3650...
- For example,
- Check your
Decide whether to set up an HTTP listener inline on the Gateway resource or as a separate ListenerSet resource. Note that ListenerSets are an experimental feature in the upstream Kubernetes Gateway API project, and subject to change. For more information, see the Listener overview.
Create self-signed TLS certificates
Create self-signed TLS certificates that you use for the mutual TLS connection between your client application (curl
) and the gateway proxy.
Self-signed certificates are used for demonstration purposes. Do not use self-signed certificates in production environments. Instead, use certificates that are issued from a trust Certificate Authority.
Create a root certificate for the
example.com
domain. You use this certificate to sign the certificate for your client and gateway later.mkdir example_certs openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example_certs/example.com.key -out example_certs/example.com.crt
Create a gateway certificate that is signed by the root CA certificate that you created in the previous step.
openssl req -out example_certs/gateway.csr -newkey rsa:2048 -nodes -keyout example_certs/gateway.key -subj "/CN=*/O=any domain" openssl x509 -req -sha256 -days 365 -CA example_certs/example.com.crt -CAkey example_certs/example.com.key -set_serial 0 -in example_certs/gateway.csr -out example_certs/gateway.crt
Create a Kubernetes secret to store your gateway TLS certificate. You create the secret in the same cluster and namespace that the gateway is deployed to. By including a
rootca
certificate, Gloo Gateway is automatically configured for mutual TLS with the downstream application.export TLS_CERT="$(< example_certs/gateway.crt)" export TLS_KEY="$(< example_certs/gateway.key)" export CA_CERT="$(< example_certs/example.com.crt)" cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Secret metadata: name: https namespace: gloo-system type: kubernetes.io/tls stringData: tls.crt: | $(echo "$TLS_CERT" | sed 's/^/ /') tls.key: | $(echo "$TLS_KEY" | sed 's/^/ /') ca.crt: | $(echo "$CA_CERT" | sed 's/^/ /') EOF
You can also runglooctl create secret tls --name https --certchain example_certs/gateway.crt --privatekey example_certs/gateway.key --rootca example_certs/example.com.crt
to create the secret with theglooctl
command line.Create a client certificate and private key. You use these credentials later when sending a request to the gateway proxy. The client certificate is signed with the same root CA certificate that you used for the gateway proxy.
openssl req -out example_certs/client.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs/client.example.com.key -subj "/CN=client.example.com/O=client organization" openssl x509 -req -sha256 -days 365 -CA example_certs/example.com.crt -CAkey example_certs/example.com.key -set_serial 1 -in example_certs/client.example.com.csr -out example_certs/client.example.com.crt
Set up an mTLS listener
Create a Gateway that is configured with the TLS certificates that you set up earlier.
Create an HTTPRoute for the httpbin app and add it to the HTTPS gateway that you created.
Verify that the HTTPRoute is applied successfully.
kubectl get httproute/httpbin-https -n httpbin -o yaml
Example output: Notice in the
status
section that the parentRef is either the Gateway or the ListenerSet, depending on how you attached the HTTPRoute.... status: parents: - conditions: - lastTransitionTime: "2025-04-29T20:48:51Z" message: "" observedGeneration: 3 reason: Accepted status: "True" type: Accepted - lastTransitionTime: "2025-04-29T20:48:51Z" message: "" observedGeneration: 3 reason: ResolvedRefs status: "True" type: ResolvedRefs controllerName: solo.io/gloo-gateway parentRef: group: gateway.networking.x-k8s.io kind: XListenerSet name: https-listenerset namespace: gloo-system
Verify that the listener now has a route attached.
Get the external address of the gateway and save it in an environment variable. Note that it might take a few seconds for the gateway address to become available.
Send a request to the httpbin app. Verify that you see the TLS handshake and that you get back a 200 HTTP response code.
Example output:
* ALPN: curl offers h2,http/1.1 * (304) (OUT), TLS handshake, Client hello (1): * (304) (IN), TLS handshake, Server hello (2): * (304) (IN), TLS handshake, Unknown (8): * (304) (IN), TLS handshake, Request CERT (13): * (304) (IN), TLS handshake, Certificate (11): * (304) (IN), TLS handshake, CERT verify (15): * (304) (IN), TLS handshake, Finished (20): * (304) (OUT), TLS handshake, Certificate (11): * (304) (OUT), TLS handshake, CERT verify (15): * (304) (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF * ALPN: server accepted h2 * Server certificate: * subject: CN=*; O=any domain * start date: Apr 30 19:20:03 2025 GMT * expire date: Apr 30 19:20:03 2026 GMT * issuer: O=any domain; CN=* * SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway. * using HTTP/2 * [HTTP/2] [1] OPENED stream for https://https.example.com:443/anything * [HTTP/2] [1] [:method: GET] * [HTTP/2] [1] [:scheme: https] * [HTTP/2] [1] [:authority: https.example.com] * [HTTP/2] [1] [:path: /anything] * [HTTP/2] [1] [user-agent: curl/8.7.1] * [HTTP/2] [1] [accept: */*] > GET /anything HTTP/2 > Host: https.example.com > User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off < HTTP/2 200 HTTP/2 200 ... { "args": {}, "headers": { "Accept": [ "*/*" ], "Host": [ "https.example.com" ], "User-Agent": [ "curl/8.7.1" ], "X-Envoy-Expected-Rq-Timeout-Ms": [ "15000" ], "X-Forwarded-Proto": [ "https" ], "X-Request-Id": [ "01ef350c-4587-4350-aa0e-0712fee757b9" ] }, "url": "https://https.example.com/anything", ... }
Cleanup
Remove the routing resources for the HTTPS route, including the Kubernetes secret that holds the TLS certificate and key.
Remove the
example_certs
directory that stores your TLS credentials.rm -rf example_certs