# mTLS

Client certificates for outgoing connections and certificate-based authentication of API clients on channels.

[Video: New mTLS definition](https://zatosource-production.b-cdn.net/docs/gfx/security/mtls-create.webm?v=1790440881)

Dashboard menu: Security > mTLS

## Overview {#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 {#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.

The definition is exported and imported with [enmasse](https://zato.io/docs/admin/enmasse.html) under the `security` key, with `type: mtls` - the fields are in the [enmasse reference](https://zato.io/docs/admin/enmasse-reference.html).

## Outgoing connections {#outgoing-connections}

Assign a definition to an [outgoing REST](https://zato.io/docs/dev/rest/outconns.html) or [SOAP](https://zato.io/docs/dev/soap/index.html) 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.

[Video: Outgoing REST with mTLS](https://zatosource-production.b-cdn.net/docs/gfx/outgoing/rest-mtls-create.webm?v=1791298934)

Dashboard menu: Connections > Outgoing > REST

The certificate material is a set of files that you mount into the container, e.g.:

```bash
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 inside the container - `/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 {#channels}

[Video: REST channel with mTLS](https://zatosource-production.b-cdn.net/docs/gfx/channels/rest-mtls-create.webm?v=1791298939)

Dashboard menu: Connections > Channels > REST

TLS for channels terminates at the proxy in front of the server - it is the proxy that completes 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](https://zato.io/docs/dev/rest/channels.html) 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`:

```bash
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](https://zato.io/docs/admin/security/ssl/index.html).

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:

```bash
$ curl --cert client-cert.pem --key client-key.pem https://localhost:11224/api/orders
```

To find a certificate's SHA256 fingerprint for the definition:

```bash
$ 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 {#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.

## Troubleshooting {#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 appear 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.

## Learn more {#learn-more}

- [Securing channels](https://zato.io/docs/admin/security/channel.html) - Which security types a channel accepts, and how to assign them
- [Securing outgoing connections](https://zato.io/docs/admin/security/outgoing.html) - Credentials Zato presents to the systems it calls
- [API keys](https://zato.io/docs/security/api-keys.html) - Per-client keys in a configurable HTTP header
- [Basic Auth](https://zato.io/docs/security/basic-auth.html) - Usernames and passwords for channels and outgoing connections
- [Bearer tokens](https://zato.io/docs/security/bearer-tokens/) - Static tokens and JWTs verified against a JWKS endpoint
- [Security groups](https://zato.io/docs/security/groups.html) - Many API client credentials on one channel or MCP gateway
- [Kerberos (SPNEGO)](https://zato.io/docs/security/kerberos/) - Negotiate authentication from a keytab, with no ticket management
- [NTLM](https://zato.io/docs/security/ntlm.html) - Windows domain credentials for outgoing connections
- [WS-Security](https://zato.io/docs/security/ws-security.html) - UsernameToken, X.509 and SAML for SOAP traffic
- [Quota tiers](https://zato.io/docs/security/quota-tiers.html) - Named rate-limit plans assigned to any number of API clients
- [TLS certificates](https://zato.io/docs/admin/security/ssl/) - Your own certificates, the self-signed ones and how to rotate them
