a7-recipe-mtls
Overview
Mutual TLS (mTLS) ensures both the client and server verify each other's identity via TLS certificates. Standard TLS only verifies the server; mTLS adds client certificate verification.
With API7 Enterprise Edition (API7 EE) and the a7 CLI, you can configure:
- Client → API7 EE mTLS: Require clients to present valid certificates.
- API7 EE → Upstream mTLS: Present client certificates when connecting to backends.
- End-to-end mTLS: Both directions simultaneously.
When to Use
- Zero-trust networking between services.
- Secure service-to-service communication in microservices.
- Compliance requirements mandating mutual authentication.
- Replace or supplement API key authentication with certificate-based auth.
- Internal APIs that should only be accessible by authorized services.
Prerequisites
- API7 EE Control Plane and at least one Gateway Group.
- a7 CLI configured with a valid token and server address.
- Certificates and private keys in PEM format.
Part 1: Client → API7 EE mTLS
Require clients to present a valid TLS certificate when connecting to API7 EE.
1. Create SSL resource with CA for client verification
a7 ssl create --gateway-group default -f - <<'EOF'
{
"id": "mtls-domain",
"cert": "<SERVER_CERTIFICATE_PEM>",
"key": "<SERVER_PRIVATE_KEY_PEM>",
"snis": ["api.example.com"],
"client": {
"ca": "<CA_CERTIFICATE_PEM>"
}
}
EOF
Fields:
cert/key: Server certificate and private key (presented to clients).snis: Server Name Indications — domain names this certificate covers.client.ca: CA certificate used to verify client certificates.client.depth: (optional) Maximum certificate chain depth for verification.
2. Create a service for the protected backend
a7 service create --gateway-group default -f - <<'EOF'
{
"id": "secure-api-service",
"name": "secure-api-service",
"upstream": {
"type": "roundrobin",
"nodes": [
{
"host": "backend",
"port": 8080,
"weight": 1
}
]
}
}
EOF
3. Create a route on the protected domain
a7 route create --gateway-group default -f - <<'EOF'
{
"id": "secure-api",
"paths": ["/api/*"],
"host": "api.example.com",
"service_id": "secure-api-service"
}
EOF
4. Test with client certificate
# With valid client cert — succeeds
curl --cert client.crt --key client.key --cacert ca.crt \
https://api.example.com:9443/api/health
# Without client cert — fails with SSL handshake error
curl --cacert ca.crt https://api.example.com:9443/api/health
Part 2: API7 EE → Upstream mTLS
Configure API7 EE to present a client certificate when connecting to backends.
1. Prepare upstream client certificates
Create the upstream client SSL certificate and CA certificate in API7 EE first, then note their IDs/names. The recommended API7 Enterprise workflow is:
- Add an SSL Certificate using the certificate that API7 EE should present to the upstream.
- Add a CA Certificate used to verify the upstream server certificate.
- Select both certificates in the published service's upstream connection configuration.
If you use a7 ssl create to upload the client SSL certificate, keep the PEM material in a local file that is not committed, and pass the file with -f.
2. Create service with HTTPS upstream
a7 service create --gateway-group default -f - <<'EOF'
{
"id": "mtls-backend-service",
"name": "mtls-backend-service",
"upstream": {
"type": "roundrobin",
"scheme": "https",
"nodes": [
{
"host": "secure-backend",
"port": 443,
"weight": 1
}
],
"tls": {
"client_cert_id": "<UPSTREAM_CLIENT_CERTIFICATE_ID>"
}
}
}
EOF
Fields:
upstream.scheme: Must be"https"for TLS connections to upstream.upstream.nodes: Backend nodes as[{ "host": "...", "port": 443, "weight": 1 }].upstream.tls.client_cert_id: Client certificate object API7 EE presents to the upstream.upstream.pass_host: Set to"pass"(default) or"rewrite"if upstream expects a specific Host header.
Configure the upstream CA certificate in the published service's upstream connection configuration if API7 EE should verify the upstream server certificate.
3. Create route using this service
a7 route create --gateway-group default -f - <<'EOF'
{
"id": "api",
"paths": ["/api/*"],
"service_id": "mtls-backend-service"
}
EOF
Part 3: End-to-End mTLS
Combine both: clients verify themselves to API7 EE, and API7 EE verifies itself to the upstream.
1. SSL for client → API7 EE mTLS
a7 ssl create --gateway-group default -f - <<'EOF'
{
"id": "frontend-mtls",
"cert": "<API7_SERVER_CERT>",
"key": "<API7_SERVER_KEY>",
"snis": ["api.example.com"],
"client": {
"ca": "<CLIENT_CA_CERT>"
}
}
EOF
2. Service for API7 EE → backend mTLS
a7 service create --gateway-group default -f - <<'EOF'
{
"id": "secure-backend-service",
"name": "secure-backend-service",
"upstream": {
"type": "roundrobin",
"scheme": "https",
"nodes": [
{
"host": "internal-service",
"port": 443,
"weight": 1
}
],
"tls": {
"client_cert_id": "<UPSTREAM_CLIENT_CERTIFICATE_ID>"
}
}
}
EOF
3. Route connecting both
a7 route create --gateway-group default -f - <<'EOF'
{
"id": "e2e-mtls-api",
"paths": ["/api/*"],
"host": "api.example.com",
"service_id": "secure-backend-service"
}
EOF
Common Patterns
Multiple domains with different CAs
# Domain A: internal services
a7 ssl create --gateway-group default -f - <<'EOF'
{
"id": "internal-mtls",
"cert": "<INTERNAL_CERT>",
"key": "<INTERNAL_KEY>",
"snis": ["internal.example.com"],
"client": {
"ca": "<INTERNAL_CA>"
}
}
EOF
# Domain B: partner services
a7 ssl create --gateway-group default -f - <<'EOF'
{
"id": "partner-mtls",
"cert": "<PARTNER_CERT>",
"key": "<PARTNER_KEY>",
"snis": ["partner.example.com"],
"client": {
"ca": "<PARTNER_CA>"
}
}
EOF
Using Secrets for certificate management
API7 EE supports certificate storage in external secret managers. Configure secrets via Dashboard or a7 CLI first.
# Create a secret reference
a7 secret create --gateway-group default -f - <<'EOF'
{
"id": "vault/mtls-certs",
"uri": "https://vault.example.com/v1/secret/data/mtls"
}
EOF
Config Sync Example
version: "1"
gateway_group: default
ssls:
- id: api-mtls
cert: |
-----BEGIN CERTIFICATE-----
<server certificate>
-----END CERTIFICATE-----
key: |
-----BEGIN RSA PRIVATE KEY-----
<server private key>
-----END RSA PRIVATE KEY-----
snis:
- api.example.com
client:
ca: |
-----BEGIN CERTIFICATE-----
<CA certificate for client verification>
-----END CERTIFICATE-----
services:
- id: secure-backend-service
name: secure-backend-service
upstream:
type: roundrobin
scheme: https
nodes:
- host: backend
port: 443
weight: 1
tls:
client_cert_id: upstream-client-cert
routes:
- id: mtls-api
paths:
- /api/*
host: api.example.com
service_id: secure-backend-service
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| SSL handshake failure (client side) | Client cert not signed by the CA in client.ca | Verify CA chain; check that client cert is signed by the correct CA |
| "no required SSL certificate" | Client didn't send a certificate | Configure client to present cert (--cert in curl) |
| 502 to upstream | Upstream rejects API7 EE's client cert | Verify upstream.tls.client_cert_id points to a certificate signed by the upstream's trusted CA |
| Certificate expired | TLS cert past validity date | Rotate certificate with a7 ssl update |
| SNI mismatch | Domain doesn't match snis list | Add the domain to the snis array |
| Command failed with 401 | Invalid token | Refresh your token using a7 context create |
| Service not found | Different gateway group | Ensure --gateway-group matches where resources were created |
This page is generated from a7-recipe-mtls/SKILL.md in the api7/a7 repository. Browse all skills on the AI Agent Skills page.