# Downstream mTLS Settings

Downstream mTLS settings control the client certificate requirements for end users connecting to Pomerium-managed routes. (To configure a client certificate presented by Pomerium to an *upstream* service, see the [TLS Client Certificate](https://www.pomerium.com/docs/reference/routes/tls.md#tls-client-certificate) setting.)

These settings are grouped in the YAML configuration file under the key `downstream_mtls`.

## Certificate Authority (CA)

**CA** is a bundle of PEM-encoded X.509 certificates that will be treated as trust anchors when verifying client certificates. Both root and intermediate CA certificates can be used as trust anchors.

Any per-route certificate authorities set using the (deprecated) [TLS Downstream Client CA](https://www.pomerium.com/docs/reference/routes/tls.md#tls-downstream-client-certificate-authority) option will be added to this CA bundle.

The Subject Name of all configured CA certificates will be advertised in the initial TLS handshake, and so should not include any sensitive information.

The CA setting is **required** for downstream mTLS.

### How to configure

The CA bundle can be specified inline (with the `ca` key or the `DOWNSTREAM_MTLS_CA` environment variable) or as a path to a file on disk (with the `ca_file` key or the `DOWNSTREAM_MTLS_CA_FILE` environment variable). It is considered an error to specify both.

**Core:**

| **Config file keys** | **Environment variables** | **Type** |
| :-- | :-- | :-- |
| `downstream_mtls.ca` | `DOWNSTREAM_MTLS_CA` | string (base64-encoded PEM certificate bundle) |
| `downstream_mtls.ca_file` | `DOWNSTREAM_MTLS_CA_FILE` | string (path to PEM certificate bundle) |

### Examples

```yaml
downstream_mtls:
  ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk...
```

```yaml
downstream_mtls:
  ca_file: /path/to/trusted/CAs.pem
```

```bash
DOWNSTREAM_MTLS_CA=LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk...
```

```bash
DOWNSTREAM_MTLS_CA_FILE=/path/to/trusted/CAs.pem
```

**Enterprise:**

These settings are not configurable in the Enterprise Console.

**Kubernetes:**

| **[Parameter name](https://www.pomerium.com/docs/deploy/k8s/reference.md#downstreammtls)** | **Type** | **Defaults** |
| :-- | :-- | :-- |
| `downstreamMtls.ca` | base64-encoded string |  |

### Examples

```yaml
downstreamMtls:
  ca: base64-encoded-string
```

## Certificate Revocation List (CRL)

CRL support is a beta feature.

Please test thoroughly with your client certificate deployment before enabling this feature in your production environment.

**CRL** is a bundle of PEM-encoded certificate revocation lists to be consulted during certificate validation. Any leaf certificates listed as revoked will not be trusted. (This setting cannot be used to revoke an intermediate CA certificate.) Delta CRLs are not supported.

Additionally, when [**Enforcement Mode**](#enforcement-mode) is set to `reject_connection`, any provided CRLs will be considered valid only for the time period between their `thisUpdate` and `nextUpdate` timestamps. If a provided CRL is no longer valid, *no certificates from the corresponding CA will be trusted*. When using CRLs in combination with this mode, you must be prepared to regularly update all provided CRLs.

### How to configure

A CRL bundle can be specified inline (with the `crl` key or the `DOWNSTREAM_MTLS_CRL` environment variable) or as a path to a file on disk (with the `crl_file` key or the `DOWNSTREAM_MTLS_CRL_FILE` environment variable). It is considered an error to specify both.

**Core:**

| **Config file keys** | **Environment variables** | **Type** |
| :-- | :-- | :-- |
| `downstream_mtls.crl` | `DOWNSTREAM_MTLS_CRL` | string (base64-encoded PEM CRL bundle) |
| `downstream_mtls.crl_file` | `DOWNSTREAM_MTLS_CRL_FILE` | string (path to PEM CRL bundle) |

### Examples

```yaml
downstream_mtls:
  crl: LS0tLS1CRUdJTiBYNTA5IENSTC0tLS0tCk...
```

```yaml
downstream_mtls:
  crl_file: /path/to/CRLs.pem
```

```bash
DOWNSTREAM_MTLS_CRL=LS0tLS1CRUdJTiBYNTA5IENSTC0tLS0tCk...
```

```bash
DOWNSTREAM_MTLS_CRL_FILE=/path/to/CRLs.pem
```

**Enterprise:**

These settings are not configurable in the Enterprise Console.

**Kubernetes:**

| **[Parameter name](https://www.pomerium.com/docs/deploy/k8s/reference.md#downstreammtls)** | **Type** | **Defaults** |
| :-- | :-- | :-- |
| `downstreamMtls.crl` | base64-encoded string |  |

### Examples

```yaml
downstreamMtls:
  crl: base64-encoded-string
```

## Enforcement Mode

**Enforcement Mode** controls Pomerium's behavior when a client does not present a trusted client certificate. There are three options:

1. `policy_with_default_deny` — Pomerium will serve an HTML error page when making a request to any user-defined route without a trusted client certificate. (This is the default behavior.)

   \[Screenshot of invalid client certificate error page]

   Note that in this mode, client certificates are not required for internal Pomerium routes, such as the authenticate URL (if using the self-hosted authenticate service) or the /.pomerium/ page (the page showing details of a user's current login session).

   Any requests without a trusted client certificate will be logged by the authorize service, with the deny reason `client-certificate-required` (if no certificate was presented) or `invalid-client-certificate` (if an untrusted or invalid certificate was presented).

2. `policy` — Pomerium will not require client certificates for any routes unless explicitly required by a route policy.

   This mode allows you to configure only certain routes to require a trusted client certificate. To configure a route to require client certificates, add a policy Deny rule with the `invalid_client_certificate` criterion. (See [Pomerium Policy Language](https://www.pomerium.com/docs/internals/ppl.md) for more information.)

   As in the previous mode, Pomerium will serve an HTML error page for any routes where client certificates are required but no trusted certificate is presented, and likewise these requests will be logged by the authorize service.

3. `reject_connection` — Pomerium will require a trusted client certificate during the initial TLS handshake. Any connection attempts that do not present a trusted client certificate will be rejected.

   Users will see a browser default error page. For example:

   \[Screenshot of Chrome's client certificate error page]

   This matches the default behavior of many popular reverse proxies, such as Apache httpd and Caddy.

   Note that this mode also:

   - Requires client certificates even for internal Pomerium routes, such as the authenticate URL (if using the self-hosted authenticate service) and the /.pomerium/ page (the page showing details of a user's current login session). In particular, any upstream services that perform [JWT verification](https://www.pomerium.com/docs/capabilities/getting-users-identity.md) will not be able to access the default [JWKS endpoint](https://www.pomerium.com/docs/reference/signing-key.md#jwks-endpoint). You may need to host your own JWKS endpoint, or provide some other means of distributing the JWT verification key(s).

   - Adds a requirement that any configured [**CRL**](#crl) is valid only between its `thisUpdate` and `nextUpdate` timestamps. *If an invalid CRL is provided for a certain CA, no certificates issued by that CA will be allowed.*

   - Enables TLS connection failure logging. Any connection attempt that fails during the TLS handshake will be logged by the proxy service with the message `"listener connection failure"`. The log entry will include the client IP address, server hostname, and client certificate info (if a client certificate was presented), so you can monitor rejected connections. (Otherwise, when a connection is rejected due to mTLS requirements, the client would never have the opportunity to make any requests, and so there would be no entries in the [access log](https://www.pomerium.com/docs/reference/access-log-fields.md#access-log-behavior) or [authorize log](https://www.pomerium.com/docs/capabilities/audit-logs.md).)

### How to configure

**Core:**

| **Config file keys** | **Environment variables** | **Type** |
| :-- | :-- | :-- |
| `downstream_mtls.enforcement` | `DOWNSTREAM_MTLS_ENFORCEMENT` | string (one of `policy_with_default_deny`, `policy`, or `reject_connection`) |

### Examples

```yaml
downstream_mtls:
  enforcement: reject_connection
```

```bash
DOWNSTREAM_MTLS_ENFORCEMENT=reject_connection
```

**Enterprise:**

This setting is not configurable in the Enterprise Console.

**Kubernetes:**

| **[Parameter name](https://www.pomerium.com/docs/deploy/k8s/reference.md#downstreammtls)** | **Type** | **Defaults** |
| :-- | :-- | :-- |
| `downstreamMtls.enforcement` | string (one of `policy_with_default_deny`, `policy`, or `reject_connection`) |  |

### Examples

```yaml
downstreamMtls:
  enforcement: reject_connection
```

## Match Subject Alt Names

**Match Subject Alt Names** can be used to add an additional constraint when validating client certificates: each certificate must contain a Subject Alternative Name (SAN) of a particular type, whose value matches the provided regular expression.

Client certificates satisfying *at least one* of the provided match conditions will be allowed.

Supported SAN types include:

- `dns` — a DNS name
- `email` — an email address (this is the `rfc822Name` as specified in [RFC 5280 §4.2.1.6](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6))
- `ip_address` — an IP address (either IPv4 or IPv6)
- `uri` — a Uniform Resource Identifier (URI)
- `user_principal_name` — a User Principal Name (UPN), commonly used with smart cards

The provided regular expression must match against the entire SAN entry. The regular expressions use Google's [RE2 syntax](https://github.com/google/re2/wiki/Syntax).

SAN IPv4 addresses will be expressed in dotted decimal notation (e.g. "127.0.0.1") when matching against the provided regular expression. SAN IPv6 addresses will be expressed as specified in [RFC 5952](https://datatracker.ietf.org/doc/html/rfc5952#section-4).

### How to configure

**Core:**

| **Config file keys** | **Environment variables** | **Type** |
| :-- | :-- | :-- |
| `downstream_mtls.match_subject_alt_names` | n/a | array of mappings from SAN type to regular expression |

### Examples

To require that all client certificates have a DNS SAN with a domain name ending in ".corp.example.com":

```yaml
downstream_mtls:
  match_subject_alt_names:
    - dns: '.*\.corp\.example\.com'
```

To require that all client certificates have *either* a DNS SAN containing the string "beyondcorp" *or* an email address SAN with the domain "example.com":

```yaml
downstream_mtls:
  match_subject_alt_names:
    - dns: '.*beyondcorp.*'
    - email: '.*@example\.com'
```

**Enterprise:**

This setting is not configurable in the Enterprise Console.

**Kubernetes:**

| **[Parameter name](https://www.pomerium.com/docs/deploy/k8s/reference.md#downstreammtls)** | **Type** | **Defaults** |
| :-- | :-- | :-- |
| `downstreamMtls.matchSubjectAltNames` | object of string to string |  |

### Examples

```yaml
downstreamMtls:
  matchSubjectAltNames:
    dns: '.*beyondcorp.*'
    email: '.*@example\.com'
```

## Max Verify Depth

**Max Verify Depth** sets a limit on the depth of a certificate chain presented by the client. The depth is defined as the number of certificates in the verified trust chain, *excluding* the trust anchor. (The trust anchor can be any root or intermediate CA certificate included in the trusted [**CA**](#ca) bundle.) This limit does not override any `pathLenConstraint` set within the CA certificates themselves (see [RFC 5280 §4.2.1.9](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9)).

When set to 0, no specific limit is enforced.

For compatibility with previous Pomerium releases, the default is 1. That is, only certificates *directly* issued by a CA included in the trusted CA bundle are allowed.

The default value for this setting may change in a future Pomerium release.

The following diagram illustrates a CA setup with a root CA, an intermediate CA, and a second intermediate CA issued by the first intermediate CA:

```mermaid
flowchart BT
    subgraph CA bundle
    R1[Root CA]
    end
    R1 --> I1[Intermediate CA 1]
    R1 --> LA(Certificate A):::leaf
    I1 --> I2[Intermediate CA 2]
    I1 --> LB(Certificate B):::leaf
    I2 --> LC(Certificate C):::leaf
    classDef leaf fill:mintcream,stroke:lightgreen
```

In this scenario, with **Max Verify Depth** set to 1, only "Certificate A" would be allowed (as it is issued directly by the root CA). When set to 2, both "Certificate A" and "Certificate B" would be allowed, but not "Certificate C." When set to 3 or greater, then all of "Certificate A," "Certificate B," and "Certificate C" would be allowed.

Note that a client presenting "Certificate B" must also include the "Intermediate CA 1" certificate, as it is not part of the trusted CA bundle. Likewise, a client presenting "Certificate C" must also include both the "Intermediate CA 1" and "Intermediate CA 2" certificates.

### How to configure

**Core:**

| **Config file keys** | **Environment variables** | **Type** |
| :-- | :-- | :-- |
| `downstream_mtls.max_verify_depth` | `DOWNSTREAM_MTLS_MAX_VERIFY_DEPTH` | unsigned integer |

### Examples

To allow client certificate chains including the leaf certificate and at most one client-supplied intermediate CA certificate (before a trusted root or intermediate CA):

```yaml
downstream_mtls:
  max_verify_depth: 2
```

```bash
DOWNSTREAM_MTLS_MAX_VERIFY_DEPTH=2
```

To remove the default limit:

```yaml
downstream_mtls:
  max_verify_depth: 0
```

```bash
DOWNSTREAM_MTLS_MAX_VERIFY_DEPTH=0
```

**Enterprise:**

This setting is not configurable in the Enterprise Console.

**Kubernetes:**

| **[Parameter name](https://www.pomerium.com/docs/deploy/k8s/reference.md#downstreammtls)** | **Type** | **Defaults** |
| :-- | :-- | :-- |
| `downstreamMtls.maxVerifyDepth` | number |  |

### Examples

```yaml
downstreamMtls:
  maxVerifyDepth: 2
```
