Skip to main content
Version: 3.19.0

Configure mTLS Between Client and APISIX

Mutual TLS (mTLS) is a two-way TLS where the client and the server authenticate each other. It is typically implemented to prevent unauthorized access and harden security.

This guide will show you how to configure mTLS between downstream client applications and APISIX.


mTLS between Client and APISIX

Prerequisite(s)​

  • Install Docker.
  • Install a curl build that supports separate PEM client certificate and key files. The commands in this guide do not cover Schannel-backed curl, which uses certificate-store paths instead of the PEM-file workflow shown here.
  • Follow the Getting Started tutorial to start a new APISIX instance in Docker or on Kubernetes.
  • Install jq to encode the PEM files as JSON strings.
  • If you use the Admin API examples and authentication is enabled, export an admin-role key as ADMIN_API_KEY. Configure the key under deployment.admin.admin_key. The Docker quickstart disables authentication for local evaluation; keep it enabled in production.

Create a Route​

Create a route that forwards all requests to /ip to the upstream httpbin.org:

curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "quickstart-ip",
"uri": "/ip",
"upstream": {
"nodes": {
"httpbin.org:80":1
},
"type": "roundrobin"
}
}'

Generate Certificates and Keys​

Generate the Certificate Authority (CA) key and certificate:

openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 36500 -sha256 \
-key ca.key \
-out ca.crt \
-subj "/CN=MyTestCA" \
-extensions v3_ca \
-config <(printf "[req]\ndistinguished_name=req\n[ v3_ca ]\nbasicConstraints=critical,CA:TRUE\nkeyUsage=critical,keyCertSign,cRLSign\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid:always,issuer")

Generate the key and certificate signing request (CSR):

openssl genrsa -out server.key 2048
openssl req -new -sha256 \
-key server.key \
-out server.csr \
-subj "/CN=test.com"

Sign the server CSR with the CA certificate to generate the server certificate:

openssl x509 -req -days 36500 -sha256 \
-in server.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt \
-extensions v3_req \
-extfile <(printf "[v3_req]\nbasicConstraints=CA:FALSE\nkeyUsage=digitalSignature,keyEncipherment\nextendedKeyUsage=serverAuth\nsubjectAltName=DNS:test.com")

Generate the key and certificate signing request (CSR) for the client:

openssl genrsa -out client.key 2048
openssl req -new -sha256 \
-key client.key \
-out client.csr \
-subj "/CN=CLIENT"

Sign the client CSR with the CA certificate to generate the client certificate:

openssl x509 -req -days 36500 -sha256 \
-in client.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt \
-extensions v3_req \
-extfile <(printf "[v3_req]\nbasicConstraints=CA:FALSE\nkeyUsage=digitalSignature,keyEncipherment\nextendedKeyUsage=clientAuth")

Configure mTLS for APISIX​

Create the SSL object with an SNI that matches the server certificate's test.com DNS name. jq reads the server certificate, private key, and CA certificate and encodes their newlines as valid JSON:

jq -n --rawfile cert server.crt --rawfile key server.key --rawfile ca ca.crt \
'{id: "quickstart-mtls-client-ssl", sni: "test.com", cert: $cert, key: $key, client: {ca: $ca}}' \
| curl -i "http://127.0.0.1:9180/apisix/admin/ssls" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @-

The client.ca field tells APISIX which CA to trust when verifying client certificates.

Verify mTLS between Client and APISIX​

With Client Certificate​

The server certificate is valid for the DNS name test.com. Use that hostname for verification while resolving it to the local APISIX address.

Send a request with the client certificate. --cacert verifies the APISIX certificate chain, and --resolve connects test.com to 127.0.0.1 without disabling hostname verification:

curl -iv --cacert ca.crt \
--noproxy test.com \
--resolve "test.com:9443:127.0.0.1" \
--cert client.crt \
--key client.key \
"https://test.com:9443/ip"

The request should succeed with an HTTP 2xx response. Relevant lines from an OpenSSL-backed curl build should resemble the following:

* subjectAltName: host "test.com" matched cert's "test.com"
* SSL certificate verify ok.
< HTTP/2 200

The exact transcript depends on the curl TLS backend and negotiated HTTP version. Verify that the output confirms both the certificate match and successful certificate verification; do not rely only on the HTTP status.

This verifies both sides of mTLS: curl trusts and verifies the APISIX certificate, while APISIX requests and accepts the client certificate.

Without Client Certificate​

Repeat the request with the same server verification, but omit the client certificate and key:

curl -iv --cacert ca.crt \
--noproxy test.com \
--resolve "test.com:9443:127.0.0.1" \
"https://test.com:9443/ip"

The earlier successful request establishes server certificate and hostname verification. APISIX should reject this request because the client did not present a certificate. With an OpenSSL-backed curl build, the rejection can resemble the following:

curl: (56) OpenSSL SSL_read: ... tlsv13 alert certificate required

Depending on the APISIX runtime and curl TLS backend, curl might instead receive an HTTP 400 Bad Request response.

With Client Certificate from an Untrusted CA​

Generate a second CA and a client certificate that APISIX does not trust:

openssl genrsa -out untrusted-ca.key 2048
openssl req -new -x509 -days 36500 -sha256 \
-key untrusted-ca.key \
-out untrusted-ca.crt \
-subj "/CN=UntrustedClientCA" \
-extensions v3_ca \
-config <(printf "[req]\ndistinguished_name=req\n[v3_ca]\nbasicConstraints=critical,CA:TRUE\nkeyUsage=critical,keyCertSign,cRLSign\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid:always,issuer")

openssl genrsa -out untrusted-client.key 2048
openssl req -new -sha256 \
-key untrusted-client.key \
-out untrusted-client.csr \
-subj "/CN=UNTRUSTED_CLIENT"
openssl x509 -req -days 36500 -sha256 \
-in untrusted-client.csr \
-CA untrusted-ca.crt -CAkey untrusted-ca.key -CAcreateserial \
-out untrusted-client.crt \
-extensions v3_req \
-extfile <(printf "[v3_req]\nbasicConstraints=CA:FALSE\nkeyUsage=digitalSignature,keyEncipherment\nextendedKeyUsage=clientAuth")

Send a request that still verifies the APISIX server with ca.crt, but presents the untrusted client certificate:

curl -iv --cacert ca.crt \
--noproxy test.com \
--resolve "test.com:9443:127.0.0.1" \
--cert untrusted-client.crt \
--key untrusted-client.key \
"https://test.com:9443/ip"

The earlier successful request establishes server certificate and hostname verification. APISIX should reject this request because the client certificate is outside the configured trust chain. With an OpenSSL-backed curl build, the rejection can resemble the following:

curl: (56) OpenSSL SSL_read: ... tlsv1 alert unknown ca

Depending on the APISIX runtime and curl TLS backend, curl might instead receive an HTTP 400 Bad Request response.

Next Steps​

You can learn more about mTLS and APISIX SSL object in SSL Certificates.

APISIX also supports TLS connection between clients and APISIX. See Configure HTTPS between client and APISIX for more details.