---
name: azion-configure-mtls-on-a-workload
description: >-
  Upload a Trusted CA certificate and a CRL, turn on mTLS on a workload, and test the handshake, in Azion Console, with the Azion CLI, or with the API.
---

# Configure mTLS on a workload

You can turn on mutual TLS ([mTLS](/en/documentation/platform/workloads/mtls/)) on a [workload](/en/documentation/platform/workloads/) in [Azion Console](https://console.azion.com), with the [Azion CLI](/en/documentation/devtools/cli/), or with the API. To serve HTTPS on your domain with a server certificate of your own instead, refer to [Upload a digital certificate](/en/documentation/guides/application-security/tls-and-certificates/digital-certificates/).

With mTLS on, the workload asks each client for a certificate during the TLS handshake. It checks that certificate against a Trusted CA certificate: the certificate of the certificate authority (CA) that signs your clients' certificates. The setup takes three objects: the Trusted CA certificate, an optional certificate revocation list (CRL) from the same CA, and the `mtls` object of the workload.

An account that runs on API v3 has Domains in place of workloads, and sets mTLS on each domain. Turn on mTLS on a v3 domain carries those steps.

---

## Prerequisites

### Console

- mTLS activated on your account. Azion activates mTLS per account, so contact the Azion Sales team to turn it on.
- A workload whose deployment names an application, served over HTTPS. The client certificate travels in the TLS handshake, so mTLS checks HTTPS connections only. To create a workload, refer to [Workloads quickstart](/en/documentation/platform/workloads/quickstart/).
- The certificate of your CA, in PEM format. A third-party CA issues it. A certificate that Azion generates cannot serve as the Trusted CA.
- (Optional) A CRL in PEM format, signed by the same CA.
- A client certificate that CA signed, with its private key, to test the workload.
- Access to Azion Console. For more information, refer to [How to access Azion Console](/en/documentation/guides/platform/account-and-billing/how-to-access-azion-console/).

### CLI

- mTLS activated on your account. Azion activates mTLS per account, so contact the Azion Sales team to turn it on.
- A workload whose deployment names an application, served over HTTPS. The client certificate travels in the TLS handshake, so mTLS checks HTTPS connections only. To create a workload, refer to [Workloads quickstart](/en/documentation/platform/workloads/quickstart/).
- The certificate of your CA, in PEM format. A third-party CA issues it. A certificate that Azion generates cannot serve as the Trusted CA.
- (Optional) A CRL in PEM format, signed by the same CA.
- A client certificate that CA signed, with its private key, to test the workload.
- The [Azion CLI](/en/documentation/devtools/cli/), authorized with your account. This page matches Azion CLI 4.23.0.
- The workload ID. `azion create workload` prints it as `Created Workload with ID <workload-id>`.

### API

- mTLS activated on your account. Azion activates mTLS per account, so contact the Azion Sales team to turn it on.
- A workload whose deployment names an application, served over HTTPS. The client certificate travels in the TLS handshake, so mTLS checks HTTPS connections only. To create a workload, refer to [Workloads quickstart](/en/documentation/platform/workloads/quickstart/).
- The certificate of your CA, in PEM format. A third-party CA issues it. A certificate that Azion generates cannot serve as the Trusted CA.
- (Optional) A CRL in PEM format, signed by the same CA.
- A client certificate that CA signed, with its private key, to test the workload.
- A personal token for the `Authorization` header, in the form `Token [TOKEN VALUE]`. To create a token, refer to [Personal tokens](/en/documentation/fundamentals/personal-tokens/).
- `curl` or another HTTP client.
- The workload ID. `azion create workload` prints it as `Created Workload with ID <workload-id>`.

---

## Upload the Trusted CA certificate

[Certificate Manager](/en/documentation/platform/workloads/#certificate-manager) stores the Trusted CA certificate with the type `trusted_ca_certificate` and no private key. The file must be in PEM format, with its `-----BEGIN CERTIFICATE-----` and `-----END CERTIFICATE-----` lines, and Certificate Manager refuses any other format. When your chain has intermediate certificates, include them in the same file.

### Console

Azion Console uploads the Trusted CA certificate from the form that names it. The **Trusted CA Certificate** field closes with **Create Digital Trusted CA certificate**, which takes the PEM file without leaving the form. Turn on mTLS on the workload carries the steps, and Turn on mTLS on a v3 domain carries them for a domain.

### CLI

To upload the certificate with the Azion CLI, pass the PEM file of your CA, here `ca.pem`:

```bash
azion create digital-certificate --name my-trusted-ca --certificate ca.pem --certificate-type trusted_ca_certificate
```

The command prints the ID of the new certificate. The workload update in Turn on mTLS on the workload needs it:

```text
Created Digital Certificate with ID <trusted-ca-id>
```

### API

To upload the certificate with the API, send a `POST` request to the certificates endpoint. Write the certificate as one string, with each line break as `\n`:

```bash
curl --request POST \
  --url https://api.azion.com/v4/workspace/tls/certificates \
  --header 'Accept: application/json' \
  --header 'Authorization: Token [TOKEN VALUE]' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "my-trusted-ca",
  "type": "trusted_ca_certificate",
  "certificate": "-----BEGIN CERTIFICATE-----\n<certificate-body>\n-----END CERTIFICATE-----\n"
}'
```

The API answers `201` with the new certificate. Keep its `id` for the workload update.

The Trusted CA certificate reads `inactive` in Certificate Manager until a workload names it in its `mtls` object.

---

## Upload a certificate revocation list

A CRL lists the certificates that a CA revoked before their expiration date, and the CA signs it. A workload with mTLS on names up to 100 CRLs in `mtls.config.crl`. This task is optional: skip it when your CA publishes no CRL.

### Console

Azion Console uploads the CRL from the workload itself. The **Certificate Revocation List (CRL)** field closes with **Create Digital CRL certificate**, which takes the PEM file without leaving the form. Turn on mTLS on the workload carries the steps.

### CLI

To upload the CRL with the Azion CLI, pass its PEM file, here `ca.crl`, and the name of the CA that issued it:

```bash
azion create crl --name my-crl --issuer "My CA" --crl ca.crl
```

The command prints the ID of the new CRL:

```text
Created Certificate Revocation List with ID <crl-id>
```

### API

To upload the CRL with the API, send a `POST` request to the CRLs endpoint. Write the CRL as one string, with each line break as `\n`:

```bash
curl --request POST \
  --url https://api.azion.com/v4/workspace/tls/crls \
  --header 'Accept: application/json' \
  --header 'Authorization: Token [TOKEN VALUE]' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "my-crl",
  "issuer": "My CA",
  "crl": "-----BEGIN X509 CRL-----\n<crl-body>\n-----END X509 CRL-----\n"
}'
```

The API answers `201` with the new CRL. Keep its `id` for the workload update.

Certificate Manager stores the CRL and reads its `last_update` and `next_update` dates from the CRL itself.

---

## Turn on mTLS on the workload

The `mtls` object of the workload turns the check on and names the Trusted CA certificate, the CRLs, and the verification mode. Pick the mode in `verification`:

- `enforce`: the workload ends the handshake when a client presents no certificate, or a certificate the Trusted CA did not sign.
- `permissive`: every client completes the handshake, and a firewall rule decides which requests to refuse.

For what each mode does with each kind of client, refer to [Verification modes](/en/documentation/platform/workloads/mtls/#verification-modes).

### Console

To turn on mTLS in Azion Console:

1. **Open the Workloads page**

   Go to [Azion Console](https://console.azion.com/) > **Workloads**.

2. **Open the workload**

   Select the workload whose deployment names the application. Its edit form opens.

3. **Turn on HTTPS support**

   In the **Protocol Settings** section, turn on **HTTPS support**. Until then **Mutual Authentication** stays disabled, and the form says `You can active this setting enabling HTTPS support above.`

4. **Turn on Mutual Authentication**

   In the **Mutual Authentication Settings** section, turn on **Mutual Authentication**.

5. **Choose the verification mode**

   In **Mode**, select **Enforce** or **Permissive**. The form selects **Enforce** when the switch goes on.

6. **Select the Trusted CA certificate**

   In **Trusted CA Certificate**, select the certificate of your CA. The field is required, and **Create Digital Trusted CA certificate**, at the end of the list, uploads one without leaving the form.

7. **Select the CRLs**

   Optionally, in **Certificate Revocation List (CRL)**, select the CRLs the workload checks. **Create Digital CRL certificate** uploads one without leaving the form.

8. **Save the workload**

   Select **Save**.

### CLI

To turn on mTLS with the Azion CLI, save a JSON file with the workload ID and the `mtls` object, here as `mtls.json`. Send `null` in `crl` when the workload names no CRL:

```json
{
  "id": <workload-id>,
  "mtls": {
    "enabled": true,
    "config": { "certificate": <trusted-ca-id>, "crl": [<crl-id>], "verification": "enforce" }
  }
}
```

Update the workload with the file:

```bash
azion update workload --file mtls.json
```

The command prints the ID of the workload it updated:

```text
Updated Workload with ID <workload-id>
```

To confirm the change, describe the workload:

```bash
azion describe workload --workload-id <workload-id> --format json
```

This excerpt of the output shows the `mtls` object as the workload stores it:

```json
{
 "mtls": { "config": { "certificate": <trusted-ca-id>, "crl": [<crl-id>], "verification": "enforce" }, "enabled": true }
}
```

### API

To turn on mTLS with the API, send a `PATCH` request to the workload with the `mtls` object. Send `null` in `crl` when the workload names no CRL:

```bash
curl --request PATCH \
  --url https://api.azion.com/v4/workspace/workloads/<workload-id> \
  --header 'Accept: application/json' \
  --header 'Authorization: Token [TOKEN VALUE]' \
  --header 'Content-Type: application/json' \
  --data '{
  "mtls": {
    "enabled": true,
    "config": { "certificate": <trusted-ca-id>, "crl": [<crl-id>], "verification": "enforce" }
  }
}'
```

The API accepts the update.

The Trusted CA certificate now reads `active` in Certificate Manager. The new mode takes several minutes to reach all of Azion's distributed infrastructure. Until then, some requests still meet the previous configuration, so repeat a test before you trust its result.

The update fails when a certificate ID sits in the wrong field. A server certificate in `mtls.config.certificate` is refused with `Invalid certificate type, MUST be a Trusted CA.` A Trusted CA certificate in `tls.certificate` is refused with `Invalid certificate type, MUST be an Edge Certificate.` The CLI prints the message inside `Error: Failed to update the Workload: [...]`. For both fixes, refer to [mTLS errors](/en/documentation/platform/workloads/mtls/#errors).

---

## Turn on mTLS on a v3 domain

An account that runs on API v3 has Domains in place of workloads, and sets mTLS on each domain. To find out which model your account runs on, refer to [Verify your account migration](/en/documentation/guides/application-security/access-and-compliance/verify-account-migration/).

The rest of this page holds for a v3 domain as it is written, with one difference each: Upload the Trusted CA certificate is the same task, in the same Certificate Manager; Deny requests without a valid client certificate is the same rule, on the firewall the domain names in `edge_firewall_id` rather than the one in a workload's deployment; Pass client certificate details to the origin is the same rule, on the application the domain names. A domain takes no certificate revocation list, so Upload a certificate revocation list does not apply.

**Console**

To turn on mTLS on a domain in Azion Console:

1. **Open the domain**

   Go to [Azion Console](https://console.azion.com/) > **Domains**, and select the domain.

2. **Turn on Mutual Authentication**

   In the **Mutual Authentication Settings** section, turn on **Mutual Authentication**.

3. **Choose the verification mode**

   In **Mode**, select **Enforce** or **Permissive**.

4. **Select the Trusted CA certificate**

   In **Trusted CA Certificate**, select the certificate of your CA. The field is required, and **Create Digital Trusted CA certificate**, at the end of the list, uploads one without leaving the form.

5. **Save the domain**

   Select **Save**.

**API v3**

To turn on mTLS with API v3, send the three mTLS fields with the rest of the domain. The request carries the header `Accept: application/json; version=3`:

```bash
curl --request POST \
  --url https://api.azionapi.net/domains \
  --header 'Accept: application/json; version=3' \
  --header 'Authorization: Token [TOKEN VALUE]' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "mydomain.com",
  "cnames": ["mydomain.com"],
  "digital_certificate_id": null,
  "edge_application_id": <application-id>,
  "is_mtls_enabled": true,
  "mtls_verification": "enforce",
  "mtls_trusted_ca_certificate_id": <trusted-ca-id>
}'
```

For each of the three fields, refer to [Domains](/en/documentation/platform/workloads/domains/#support-for-mutual-transport-layer-security-mtls).

---

## Test the handshake

Two requests to a domain of the workload show whether it checks clients: one without a client certificate, and one with a certificate the Trusted CA signed. Send them after the mTLS change propagates.

To send a request with no client certificate, run `curl` in verbose mode:

```bash
curl -skv https://<your-domain>/ -o /dev/null
```

In `enforce` mode, the workload requests a certificate, the client sends none, and the handshake fails. `curl` exits with code `56`. With `curl` built on LibreSSL, the verbose output ends like this:

```text
* (304) (IN), TLS handshake, Request CERT (13):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Certificate (11):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384 / [blank] / UNDEF
* LibreSSL SSL_read: LibreSSL/3.3.6: error:1404C45C:SSL routines:ST_OK:reason(1116), errno 0
```

To send a request with the client certificate, pass the certificate and its private key, here `client.pem` and `client.key`:

```bash
curl -sk --cert client.pem --key client.key https://<your-domain>/ -o /dev/null -w '%{http_code}\n'
```

The handshake completes, and `curl` prints the status code your application returns:

```text
200
```

The same results hold on the workload domain, `<id>.map.azionedge.net`. In `permissive` mode, both requests complete the handshake and reach the application, and so does a request with a certificate another CA signed.

---

## Deny requests without a valid client certificate

In `permissive` mode, the workload refuses no client, so a rule in [Rules Engine for Firewall](/en/documentation/platform/firewall/rules-engine/) must refuse the requests whose client certificate failed the check. The rule acts only when the firewall is in the workload's deployment. To bind one, refer to [Bind a firewall to a workload](/en/documentation/guides/application-security/firewall-and-waf/firewall-protect-your-domain/).

This rule denies the requests to `<your-domain>` whose client certificate did not pass validation:

```json
{
  "name": "deny-unverified-clients",
  "active": true,
  "criteria": [
    [
      { "variable": "${host}", "conditional": "if", "operator": "is_equal", "argument": "<your-domain>" },
      { "variable": "${client_certificate_validation}", "conditional": "and", "operator": "is_not_equal", "argument": "true" }
    ]
  ],
  "behaviors": [{ "type": "deny" }]
}
```

To create the same rule in Azion Console:

1. **Open the firewall**

   Go to [Azion Console](https://console.azion.com/) > **Firewall**, and select the firewall in the workload's deployment.

2. **Open the Create Rule drawer**

   Select the **Rules Engine** tab, then **Rule**.

3. **Name the rule**

   Enter a name that says which requests the rule refuses.

4. **Match the domain**

   In **Criteria**, select the variable **Host** and the operator **is equal**, and enter your domain as the argument.

5. **Match the failed verification**

   Select **And**, then the variable **Client Certificate Validation** and the operator **is not equal**. The argument is a list with one entry, **Valid**, which arrives selected.

6. **Add the behavior**

   In **Behavior**, select **Deny (403 Forbidden)**.

7. **Save the rule**

   Select **Save**.

For every variable, operator, and behavior a firewall rule takes, refer to [Rules Engine for Firewall](/en/documentation/platform/firewall/rules-engine/).

Once the rule propagates, a request to `<your-domain>` without a valid client certificate receives `403 Forbidden`. A client whose certificate the Trusted CA signed still reaches the application.

---

## Pass client certificate details to the origin

The Open Banking model requires the origin to receive the client certificate in request headers, from the variables `${ssl_client_escaped_cert}` and `${ssl_client_s_dn_parsed}`. `${ssl_client_escaped_cert}` holds the client certificate as a URL-encoded PEM string, and `${ssl_client_s_dn_parsed}` holds its subject Common Name (CN). A Request Phase rule in [Rules Engine for Applications](/en/documentation/platform/applications/rules-engine/) adds each header with the `add_request_header` behavior, on the application in the workload's deployment.

This rule adds the client certificate to every request that carries one, in the `Escaped-Client-Cert` header:

```json
{
  "name": "mtls-client-certificate",
  "active": true,
  "criteria": [[{ "variable": "${ssl_client_escaped_cert}", "conditional": "if", "operator": "exists", "argument": "" }]],
  "behaviors": [{ "type": "add_request_header", "attributes": { "value": "Escaped-Client-Cert: ${ssl_client_escaped_cert}" } }]
}
```

The header argument takes the form `Header-Name: value`, and Azion Console refuses any other shape with `Header must follow the header-name: value format`. Add a second rule of the same shape for `${ssl_client_s_dn_parsed}`, under a header name of your choice.

To create the same rule in Azion Console:

1. **Open the application**

   Go to [Azion Console](https://console.azion.com/) > **Applications**, and select the application in the workload's deployment.

2. **Create a rule**

   Select the **Rules Engine** tab, then **+ Rule**.

3. **Name the rule**

   Enter a name that says which header the rule adds.

4. **Choose the phase**

   In the **Phase** section, select **Request Phase**. The choice is final once the rule is created.

5. **Match a request that carries a certificate**

   In **Criteria**, select the variable `${ssl_client_escaped_cert}` and the operator **exists**.

6. **Add the behavior**

   In **Behavior**, select **Add Request Header** and enter `Escaped-Client-Cert: ${ssl_client_escaped_cert}` as the argument.

7. **Save the rule**

   Select **Save**.

For every variable and behavior an application rule takes, refer to [Rules Engine for Applications](/en/documentation/platform/applications/rules-engine/). The other client certificate variables are listed in [mTLS variables](/en/documentation/platform/applications/rules-engine/#mutual-transport-layer-security-mtls-variables).

Once the rules propagate, each request that Azion sends to the origin for a client with a certificate carries the headers.

---

## Next steps

- [mTLS](/en/documentation/platform/workloads/mtls.md): Look up every field of the mtls object, what each verification mode does, and the errors.
- [Bind a firewall to a workload](/en/documentation/guides/application-security/firewall-and-waf/firewall-protect-your-domain.md): Put a firewall in the workload's deployment, so its rules can deny unverified clients.
- [Rules Engine for Applications](/en/documentation/platform/applications/rules-engine.md): Find every client certificate variable a rule can read and pass to the origin.
- [Troubleshoot Workloads](/en/documentation/platform/workloads/troubleshooting.md): Find why a certificate is refused or a TLS handshake fails.
