mTLS
Client certificates for outgoing connections and certificate-based authentication of API clients on channels.
Overview
An mTLS security definition describes authentication with client certificates - during the TLS handshake the client presents its own certificate and the server verifies it, so both sides authenticate instead of just the server. There are no headers and no tokens, the authentication happens entirely at the TLS layer.
One definition type serves both directions of traffic, and each direction uses a different set of fields:
- Outgoing - Zato presents a client certificate when it calls a remote REST or SOAP endpoint that requires one. The definition holds paths to the certificate material mounted into the container.
- Channels - Zato authenticates API clients by the client certificate they presented to the TLS-terminating proxy in front of the server. The definition contains the match criteria - the expected certificate fingerprint or subject DN.
Definitions are managed in the dashboard under Security > mTLS. There is no password and no change-password action - the certificate material is the credential. Changes take effect immediately, without server restarts.
Definition fields
Fields used by outgoing connections:
| Field | Notes |
|---|---|
| Name | A unique name for the definition |
| Certificate path | Path to the client certificate in PEM format, as mounted into the container |
| Key path | Path to the certificate's private key in PEM format. Leave empty when the certificate file already contains the key. |
| CA certificates path | Optional path to a CA bundle that the remote server's certificate must chain up to. When set, it replaces the connection's default TLS verification. |
Fields used by channels:
| Field | Notes |
|---|---|
| Client certificate fingerprint | The SHA256 fingerprint of the client's certificate, as a hex string. Colon separators and letter case do not matter. |
| Client certificate subject DN | The subject DN of the client's certificate, matched by exact string equality against what the proxy reports. |
For channels, configure the fingerprint or the subject DN - the fingerprint takes precedence when both are set. A definition with neither accepts any certificate that the proxy verified against the client CA.
Outgoing connections
Assign a definition to an outgoing REST or SOAP connection by selecting mTLS/<name> in the connection's security dropdown. From then on, every call through that connection presents the definition's client certificate during the handshake.
The certificate material is a set of files that you mount into the container, e.g.:
docker run \
--mount type=bind,source=/path/to/client-cert.pem,target=/opt/hot-deploy/ssl/client-cert.pem \
--mount type=bind,source=/path/to/client-key.pem,target=/opt/hot-deploy/ssl/client-key.pem \
zatosource/zato-4.1
The definition then points to the paths as the container sees them - /opt/hot-deploy/ssl/client-cert.pem and /opt/hot-deploy/ssl/client-key.pem in the example above.
When the CA certificates path is set, the remote server's certificate must chain up to that CA - this covers remote endpoints with certificates from a private PKI. When it is empty, the connection's own TLS verification settings apply, e.g. the system trust store.
Channels
TLS for channels terminates at the proxy in front of the server - it is the proxy that sees the handshake and verifies the client certificate against the client CA, not the server itself. After verification, the proxy reports the result in headers injected into each request:
| Header | Value |
|---|---|
| X-Zato-SSL-Client-Verify | SUCCESS when the certificate verified against the client CA |
| X-Zato-SSL-Client-SHA256 | The SHA256 fingerprint of the certificate, as a hex string |
| X-Zato-SSL-Client-Subject-DN | The certificate's subject DN |
A channel with an mTLS definition accepts a request when the verify header reports success and the definition's match criteria agree with the reported fingerprint or subject DN. Everything else receives a 401 response.
To enable client certificates in a container, mount the CA that signs your clients' certificates as client-ca.pem:
docker run \
--mount type=bind,source=/path/to/client-ca.pem,target=/opt/hot-deploy/ssl/client-ca.pem \
zatosource/zato-4.1
When the file is present, the container's TLS port 11224 requests client certificates during the handshake. By default the certificate is optional - clients without one still connect, and only channels with mTLS definitions reject them. To reject any client without a valid certificate at the handshake itself, set Zato_SSL_Client_Verify=required - details in the SSL/TLS chapter.
The main server certificate is unchanged - mTLS only adds the client CA file next to the existing zato.pem.
An invocation with a client certificate:
To find a certificate's SHA256 fingerprint for the definition:
$ openssl x509 -in client-cert.pem -noout -fingerprint -sha256
sha256 Fingerprint=9F:86:D0:81:88:4C:7D:65:9A:2F:EA:A0:C5:5A:D0:15:...
The colons can stay or go - both sides of the comparison are normalized before matching.
External load balancers
If your own load balancer terminates TLS in front of Zato, the channel side works identically - configure the load balancer to verify client certificates and to inject the same three headers, and the definitions match against them without any other changes. Make sure the load balancer strips any X-Zato-SSL-* headers arriving from clients before injecting its own, otherwise a client could claim a verified certificate by sending the headers itself - the container's built-in proxy already does this.
Enmasse
mTLS definitions are exported and imported with enmasse under the security key, with type: mtls. All the fields round-trip.
security:
# An outgoing-oriented definition with paths to the certificate material
- name: partner.api.client.cert
type: mtls
cert_path: /opt/hot-deploy/ssl/client-cert.pem
key_path: /opt/hot-deploy/ssl/client-key.pem
ca_certs_path: /opt/hot-deploy/ssl/partner-ca.pem
# A channel-oriented definition with the match criteria
- name: partner.inbound.cert
type: mtls
client_cert_fingerprint: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
client_cert_subject_dn: CN=partner.client,O=Partner,C=US
channel_rest:
- name: api.orders
service: orders.get-list
url_path: /api/orders
security: partner.inbound.cert
outgoing_rest:
- name: partner.api
host: https://api.partner.example.com
url_path: /orders
security: partner.api.client.cert
| Field | Notes |
|---|---|
| name | The definition's name |
| type | Always mtls |
| cert_path | Path to the client certificate, PEM |
| key_path | Path to the private key, PEM - optional when the certificate file contains the key |
| ca_certs_path | Optional CA bundle the remote server's certificate must chain up to |
| client_cert_fingerprint | Expected SHA256 fingerprint of an inbound client certificate |
| client_cert_subject_dn | Expected subject DN of an inbound client certificate |
Troubleshooting
Channel-side rejections are logged in the server log with the path and the correlation ID (cid), with the cause in parentheses:
| Log message | Cause |
|---|---|
401 Unauthorized path_info ... (No client certificate) | The verify header is absent or does not report success - the client presented no certificate, or the certificate did not verify against the client CA, or the request arrived through the plain-text port |
401 Unauthorized path_info ... (Fingerprint mismatch) | The certificate verified but its fingerprint is not the configured one |
401 Unauthorized path_info ... (Subject DN mismatch) | The certificate verified but its subject DN is not the configured one |
Outgoing-side failures surface as TLS errors from the remote server, logged with the connection's name - typically SSLError with handshake failure or certificate required when the remote endpoint rejects the presented certificate, and certificate verify failed when the remote server's own certificate does not chain up to the configured CA bundle.