mTLS
Mutual TLS (mTLS) extends TLS with client certificate authentication: AI Gateway verifies the connecting agent's certificate, in addition to the agent verifying AI Gateway's certificate. This suits deployments where every calling agent or service is provisioned with its own client certificate, instead of, or in addition to, an OAuth guard flow.
Requiring Client Certificates
Add a trust store containing the certificate authority for agent certificates to the same filesystem vault used for AI Gateway's own key, then reference the trusted certificate alias from the tls server binding and set options.mutual to required:
vaults:
server_vault:
type: filesystem
options:
keys:
store: ${{env.KEYSTORE_PATH}}
type: ${{env.KEYSTORE_TYPE}}
password: ${{env.KEYSTORE_PASSWORD}}
trust:
store: ${{env.TRUSTORE_PATH}}
type: ${{env.TRUSTORE_TYPE}}
password: ${{env.TRUSTORE_PASSWORD}}
guards:
agent_x509:
type: x509
options:
identity: subject.cn
roles:
agent:
- issuer.cn: ${{env.CA_ISSUER_CN}}
bindings:
north_tcp_server:
type: tcp
kind: server
options:
host: 0.0.0.0
port: 8443
exit: north_tls_server
north_tls_server:
type: tls
kind: server
vault: server_vault
options:
keys:
- ${{env.SERVER_CERT_ALIAS}}
trust:
- ${{env.CA_CERT_ALIAS}}
sni:
- ${{env.SERVER_HOSTNAME}}
mutual: required
authorization:
agent_x509:
credentials:
certificates: pem
routes:
- guarded:
agent_x509:
- agent
exit: north_http_server
north_http_server:
type: http
kind: server
options:
access-control:
policy: cross-origin
routes:
- when:
- headers:
":path": /mcp
exit: north_mcp_server
north_mcp_server:
type: mcp
kind: server
exit: north_mcp_proxyWith mutual: required, AI Gateway rejects the TLS handshake for any agent that does not present a certificate signed by a trusted authority, before the connection reaches the http server or mcp server bindings.
See the tls server binding reference for the full set of options fields, including options.mutual.
Authorizing by Certificate Identity
Requiring a trusted certificate only proves the agent's chain was signed by a certificate authority Zilla trusts; it says nothing about which agent connected. The x509 guard closes that gap: fed the verified peer chain through the tls server binding's own options.authorization, it extracts an identity, attributes, and roles from certificate fields (subject, issuer, subject alternative names), so routes[].guarded can authorize by role the same way it would for a JWT bearer token.
vaults:
server_vault:
type: filesystem
options:
keys:
store: ${{env.KEYSTORE_PATH}}
type: ${{env.KEYSTORE_TYPE}}
password: ${{env.KEYSTORE_PASSWORD}}
trust:
store: ${{env.TRUSTORE_PATH}}
type: ${{env.TRUSTORE_TYPE}}
password: ${{env.TRUSTORE_PASSWORD}}
guards:
agent_x509:
type: x509
options:
identity: subject.cn
roles:
agent:
- issuer.cn: ${{env.CA_ISSUER_CN}}
bindings:
north_tcp_server:
type: tcp
kind: server
options:
host: 0.0.0.0
port: 8443
exit: north_tls_server
north_tls_server:
type: tls
kind: server
vault: server_vault
options:
keys:
- ${{env.SERVER_CERT_ALIAS}}
trust:
- ${{env.CA_CERT_ALIAS}}
sni:
- ${{env.SERVER_HOSTNAME}}
mutual: required
authorization:
agent_x509:
credentials:
certificates: pem
routes:
- guarded:
agent_x509:
- agent
exit: north_http_server
north_http_server:
type: http
kind: server
options:
access-control:
policy: cross-origin
routes:
- when:
- headers:
":path": /mcp
exit: north_mcp_server
north_mcp_server:
type: mcp
kind: server
exit: north_mcp_proxyagent_x509 grants the agent role to any chain issued by the certificate authority named in ${{env.CA_ISSUER_CN}}; the guarded route on north_tls_server then only forwards to north_http_server for a connection that was granted that role. A trusted-but-unmatched certificate still completes the TLS handshake — mutual: required already guaranteed that — but the request never reaches an upstream. Nothing about this requires giving up the OAuth guard: an x509 guard here and a jwt guard on the mcp server binding downstream can be layered, one gating the transport, the other the application session.
See the x509 guard reference for the full field vocabulary (subject.*, issuer.*, san.*) and role-matching rules, and routes[].with.certificate for selecting which client certificate Zilla itself presents when AI Gateway is the one being authenticated, on an outbound tls client binding instead.

