Client mTLS Authentication
Mutual TLS (mTLS) extends standard TLS by requiring both the client and the server to present certificates during the TLS handshake. This provides cryptographic proof of client identity and is suitable for zero-trust architectures, machine-to-machine communication, and environments where API key or token authentication is not enough on its own.
In API7 Gateway, client mTLS is configured on an SSL object that binds the listener domain (SNI) to a server certificate, a private key, and the CA certificates used to verify clients. When mTLS is enabled, the gateway requires the client to present a certificate signed by one of the configured CAs before the TLS handshake completes.
This page covers mTLS between API clients and the gateway. For mTLS between the Control Plane and Data Plane, see Mutual TLS between CP and DP. For mTLS between the gateway and upstream servers, see Upstream mTLS.
How it works
- A client initiates a TLS connection to the gateway and includes the target domain in the SNI extension of the
ClientHello. - The gateway matches the SNI to an SSL object that has client mTLS configured.
- The gateway presents its server certificate and asks the client for a certificate.
- The client presents its certificate. The gateway validates the chain against the configured CA.
- If validation succeeds, the TLS handshake completes and the request is forwarded to the upstream. If validation fails, the connection is rejected at the TLS layer.
Prerequisites
Prepare the following PEM-encoded certificates:
| Certificate | Purpose |
|---|---|
| Server certificate + private key | Presented by the gateway to clients. |
| CA certificate | Used by the gateway to verify client certificates. |
| Client certificate + private key | Presented by clients to the gateway. Must be signed by the CA above. |
If you do not have a set of certificates for testing, generate one with:
# Generate CA
openssl req -x509 -newkey rsa:2048 -keyout ca.key -out ca.crt -days 365 -nodes \
-subj "/CN=Test CA"
# Generate server certificate
openssl req -newkey rsa:2048 -keyout server.key -out server.csr -nodes \
-subj "/CN=api.example.com"
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt -days 365
# Generate client certificate
openssl req -newkey rsa:2048 -keyout client.key -out client.csr -nodes \
-subj "/CN=test-client"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt -days 365
You also need a route to send traffic through. The example below uses an httpbin.org service with a route on /anything.
Configure client mTLS
Step 1: Create a service and route
Client mTLS is enforced during the TLS handshake based on SNI selection, before any routing decision. A route is not required to verify the handshake itself, but you need one to receive a meaningful HTTP response from an upstream. Create a minimal service and route first.
- Admin API
- ADC
# Create the service with the upstream
curl -k "https://localhost:7443/apisix/admin/services/httpbin?gateway_group_id={group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "httpbin",
"upstream": {
"type": "roundrobin",
"scheme": "http",
"nodes": [
{ "host": "httpbin.org", "port": 80, "weight": 1 }
]
}
}'
# Create the route under that service
curl -k "https://localhost:7443/apisix/admin/routes/get-anything?gateway_group_id={group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "get-anything",
"service_id": "httpbin",
"paths": ["/anything"],
"methods": ["GET"]
}'
Replace {group_id} with your gateway group ID — use default for the gateway group created by the quickstart.
services:
- name: httpbin
upstream:
type: roundrobin
scheme: http
nodes:
- host: httpbin.org
port: 80
weight: 1
routes:
- name: get-anything
uris:
- /anything
methods:
- GET
adc sync -f adc.yaml
Step 2: Create the SSL object with client mTLS enabled
Create an SSL object that binds the SNI (api.example.com), the server certificate and key, and the CA used to verify client certificates. Setting the client.ca field is what enables client mTLS for that SNI.
- Admin API
- ADC
SERVER_CRT=$(awk 1 ORS='\\n' server.crt)
SERVER_KEY=$(awk 1 ORS='\\n' server.key)
CA_CRT=$(awk 1 ORS='\\n' ca.crt)
curl -k "https://localhost:7443/apisix/admin/ssls/client-mtls-ssl?gateway_group_id={group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"snis\": [\"api.example.com\"],
\"cert\": \"${SERVER_CRT}\",
\"key\": \"${SERVER_KEY}\",
\"client\": {
\"ca\": \"${CA_CRT}\"
}
}"
The awk 1 ORS='\\n' invocations preserve the multi-line PEM contents inside the JSON body by escaping newlines.
ssls:
- snis:
- api.example.com
certificates:
- certificate: |
-----BEGIN CERTIFICATE-----
<server.crt content>
-----END CERTIFICATE-----
key: |
-----BEGIN PRIVATE KEY-----
<server.key content>
-----END PRIVATE KEY-----
client:
ca: |
-----BEGIN CERTIFICATE-----
<ca.crt content>
-----END CERTIFICATE-----
adc sync -f adc.yaml
adc sync declaratively replaces all resources, so this file must contain the service, route, and SSL together. Combine it with the YAML from Step 1 before syncing.
Step 3: Verify
Send a request that includes a valid client certificate. The gateway listens for HTTPS traffic on port 9443.
curl --cacert ca.crt --cert client.crt --key client.key \
--resolve api.example.com:9443:127.0.0.1 \
https://api.example.com:9443/anything
You should receive a 200 OK response and the JSON body from httpbin.org.
Send the same request without a client certificate. The TLS handshake should fail before any HTTP request is sent:
curl --cacert ca.crt \
--resolve api.example.com:9443:127.0.0.1 \
https://api.example.com:9443/anything
curl exits with an SSL error and the gateway error log records SSL_do_handshake() failed (... peer did not return a certificate).
Reference certificates from a secret provider
Instead of embedding PEM material directly in the SSL object, you can reference certificates stored in HashiCorp Vault, AWS Secrets Manager, or Kubernetes Secrets using the $secret://... URI format. See Secure Credentials for the available providers and the URI shape for each one.
Security considerations
- Host header validation. When mTLS is enabled, the gateway verifies that the HTTP
Hostheader matches the SNI used during the TLS handshake. If they differ and the two domains have mTLS enabled with different CA certificates, the request is rejected to prevent certificate confusion attacks. - TLS session resumption. The gateway validates that resumed TLS sessions match the original SNI, so a session ticket cannot be reused against a different SNI to bypass mTLS.
- Certificate rotation. Plan for CA and client certificate rotation before expiry. During a CA rotation, configure the SSL object's
client.cato contain both the old and new CA bundles concatenated together so that clients on either CA can connect.
Next steps
- Upstream mTLS: Configure mTLS between the gateway and upstream services.
- Mutual TLS between CP and DP: Learn about the built-in mTLS for CP-DP communication.
- SSL Certificates: Understand SSL certificate management in API7 Gateway.