# Origin TLS and Origin CA

> Issue and revoke origin certificates of the OpenWAF Origin CA, check origin TLS and download the CA bundles.

Encrypt and authenticate the connection between the edge and your origin. The edge verifies origin certificates issued by the OpenWAF Origin CA in `full_strict` mode, and can present its own client certificate (mTLS) to origins that require it. See [SSL/TLS](https://developers.openwaf.dev/ssl/).

| Operation | Endpoint |
|---|---|
| [List origin certificates](https://developers.openwaf.dev/api/origin-tls/#list-origin-certs) | `GET /v1/orgs/{org}/zones/{zone}/origin-certs` |
| [Issue origin certificate](https://developers.openwaf.dev/api/origin-tls/#issue-origin-cert) | `POST /v1/orgs/{org}/zones/{zone}/origin-certs` |
| [Revoke origin certificate](https://developers.openwaf.dev/api/origin-tls/#revoke-origin-cert) | `DELETE /v1/orgs/{org}/zones/{zone}/origin-certs/{cert}` |
| [Get origin TLS status](https://developers.openwaf.dev/api/origin-tls/#get-origin-tls-status) | `GET /v1/orgs/{org}/zones/{zone}/origin-tls/status` |
| [Download the Origin CA](https://developers.openwaf.dev/api/origin-tls/#get-origin-ca) | `GET /v1/origin-tls/origin-ca.pem` |
| [Download the edge client CA](https://developers.openwaf.dev/api/origin-tls/#get-edge-client-ca) | `GET /v1/origin-tls/edge-client-ca.pem` |

## List origin certificates

```http
GET /v1/orgs/{org}/zones/{zone}/origin-certs
```

Lists the origin certificates issued for the zone, newest first, with their hostnames, key type, serial, expiry and status (`active`, `revoked`, `expired`). Private keys are never returned.

**Required role:** `readonly` or higher.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `org` | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise `404`). |
| `zone` | path | string (uuid) | Yes | Zone id (see [List zones](https://developers.openwaf.dev/api/zones/#list-zones)). |

#### Response

- `200`: Returns `items`, an array of [OriginCert](https://developers.openwaf.dev/api/schemas/#origin-cert).

| Field | Type | Description |
|---|---|---|
| `items[].id` | string (uuid) | Id. |
| `items[].zone_id` | string (uuid) | Zone id. |
| `items[].hostnames` | array of string |  |
| `items[].key_type` | string | `csr`: the public key of a certificate request was certified. One of `ecdsa_p256`, `rsa2048`, `csr`. |
| `items[].serial` | string | Lowercase hexadecimal without leading zeros. |
| `items[].fingerprint_sha256` | string | SHA-256 fingerprint of the certificate. |
| `items[].not_before` | string (date-time) | Start of validity. |
| `items[].not_after` | string (date-time) | End of validity. |
| `items[].validity_days` | integer |  |
| `items[].status` | string | revoked takes precedence over expired. One of `active`, `revoked`, `expired`. |
| `items[].created_by` | string | Who created it (actor). |
| `items[].created_at` | string (date-time) | Creation time. |
| `items[].revoked_at` | string (date-time) or null | Revocation time (null if active). |
| `items[].revoked_by` | string or null | Who revoked it. |

#### Errors

| Status | Description |
|---|---|
| `404` | Not found, or not visible to the token (`not_found`). |
| `503` | `unavailable`: the Origin CA is not available right now. |

#### Example

**curl**


```sh
curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/origin-certs" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.listOriginCerts({
  zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token from OPENWAF_TOKEN
result = owf.list_origin_certs(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d")
```



```json
{
  "items": [
    {
      "id": "a5614527-0ce6-43be-ad1d-d012b7394867",
      "zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
      "hostnames": [
        "example.com",
        "*.example.com"
      ],
      "key_type": "ecdsa_p256",
      "serial": "5f3a9c0e7b21d4a68c13e9f02b7d4a61",
      "fingerprint_sha256": "example",
      "not_before": "2026-10-27T09:30:00Z",
      "not_after": "2026-10-27T09:30:00Z",
      "validity_days": 0,
      "status": "active",
      "created_by": "token:11111111-2222-4333-8444-555555555555",
      "created_at": "2026-10-27T09:30:00Z",
      "revoked_at": "2026-10-27T09:30:00Z",
      "revoked_by": null
    }
  ]
}
```

## Issue origin certificate

```http
POST /v1/orgs/{org}/zones/{zone}/origin-certs
```

Issues a certificate signed by the OpenWAF Origin CA for names of the zone, to install on the origin server so that the edge can verify it in `full_strict` mode. The certificate has only DNS names (SAN), server authentication usage, a random 128-bit serial and the first name as CN.

OpenWAF generates the key (`key_type`) unless you send a `csr`. The private key is returned only in this answer (`Cache-Control: no-store`) and is never stored. With `dry_run=1` the request is only validated.

**Required role:** `tech` or higher.

**Change controls:** [`dry_run`](https://developers.openwaf.dev/api/#configuration-changes) · [`X-Change-Reason`](https://developers.openwaf.dev/api/#configuration-changes) (optional)

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `org` | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise `404`). |
| `zone` | path | string (uuid) | Yes | Zone id (see [List zones](https://developers.openwaf.dev/api/zones/#list-zones)). |
| `X-Change-Reason` | header | string | No | Reason of the change (1-500 UTF-8 characters, no control characters); defaults to the creation of the zone. 1 to 500 characters. |
| `dry_run` | query | string | No | `1`/`true` simulates the change: answers 200 with the plan (`Applied`) and saves nothing. `0`/`false` accepted; any other value: 400. One of `1`, `true`, `0`, `false`. |

#### Request body

Content type `application/json`. Schema: [OriginCertRequest](https://developers.openwaf.dev/api/schemas/#origin-cert-request).

| Field | Type | Required | Description |
|---|---|---|---|
| `hostnames` | array of string | Yes | The zone, names below it or wildcards `*.<zone name>`; lowercased and deduplicated. 1 to 100 items. |
| `key_type` | string | No | Key that OpenWAF generates (empty with `csr`). One of `ecdsa_p256`, `rsa2048`. Default `ecdsa_p256`. |
| `validity_days` | integer | No | One of `7`, `30`, `90`, `365`, `730`, `1095`, `5475`. Default `5475`. |
| `csr` | string | No | PEM certificate request (ECDSA P-256/P-384 or RSA 2048-8192): its public key is certified; its subject and extensions are ignored and no key is generated. |

#### Response

- `200`: Simulation. Returns [OriginCertIssued](https://developers.openwaf.dev/api/schemas/#origin-cert-issued).
- `201`: Issued. Returns [OriginCertIssued](https://developers.openwaf.dev/api/schemas/#origin-cert-issued).

| Field | Type | Description |
|---|---|---|
| `dry_run` | boolean | Only with `?dry_run=1`: what would be issued; no PEM. |
| `certificate` | [OriginCert](https://developers.openwaf.dev/api/schemas/#origin-cert) |  |
| `certificate_pem` | string |  |
| `private_key_pem` | string | PKCS#8. Only in this answer (never stored); left out with `csr`. |
| `ca_pem` | string | The OpenWAF Origin CA that signs it. |

#### Errors

| Status | Description |
|---|---|
| `400` | Malformed request (`bad_request`). |
| `403` | The token's role or limits do not allow it (`forbidden`). |
| `404` | Not found, or not visible to the token (`not_found`). |
| `409` | `zone_merged`: the zone is merged into another one (read-only until it is unmerged or purged). |
| `422` | Validation error (`invalid`) or missing or invalid reason (`reason_required`). |
| `503` | `unavailable`: the Origin CA is not available right now. |

#### Example

**curl**


```sh
curl -X POST "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/origin-certs" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "X-Change-Reason: Origin certificate for the web servers" \
  -H "Content-Type: application/json" \
  --data '{
    "hostnames": [
      "example.com",
      "*.example.com"
    ],
    "key_type": "ecdsa_p256",
    "validity_days": 5475
  }'
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.issueOriginCert({
  zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
  body: {
    hostnames: [
      "example.com",
      "*.example.com"
    ],
    key_type: "ecdsa_p256",
    validity_days: 5475
  }
}, {
  reason: "Origin certificate for the web servers"
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token from OPENWAF_TOKEN
result = owf.issue_origin_cert(
    zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
    body={
        "hostnames": [
            "example.com",
            "*.example.com"
        ],
        "key_type": "ecdsa_p256",
        "validity_days": 5475
    },
    reason="Origin certificate for the web servers",
)
```



```json
{
  "dry_run": false,
  "certificate": {
    "id": "a5614527-0ce6-43be-ad1d-d012b7394867",
    "zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
    "hostnames": [
      "example.com",
      "*.example.com"
    ],
    "key_type": "ecdsa_p256",
    "serial": "5f3a9c0e7b21d4a68c13e9f02b7d4a61",
    "fingerprint_sha256": "example",
    "not_before": "2026-10-27T09:30:00Z",
    "not_after": "2026-10-27T09:30:00Z",
    "validity_days": 0,
    "status": "active",
    "created_by": "token:11111111-2222-4333-8444-555555555555",
    "created_at": "2026-10-27T09:30:00Z",
    "revoked_at": "2026-10-27T09:30:00Z",
    "revoked_by": null
  },
  "certificate_pem": "example",
  "private_key_pem": "example",
  "ca_pem": "example"
}
```

## Revoke origin certificate

```http
DELETE /v1/orgs/{org}/zones/{zone}/origin-certs/{cert}
```

Revokes an origin certificate. The edge nodes reject it in `full_strict` mode from their next configuration, so an origin still serving it gets `526` errors. Revoking it again changes nothing. With `dry_run=1` the certificate is only returned.

**Required role:** `tech` or higher.

**Change controls:** [`dry_run`](https://developers.openwaf.dev/api/#configuration-changes) · [`X-Change-Reason`](https://developers.openwaf.dev/api/#configuration-changes) (optional)

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `org` | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise `404`). |
| `zone` | path | string (uuid) | Yes | Zone id (see [List zones](https://developers.openwaf.dev/api/zones/#list-zones)). |
| `cert` | path | string (uuid) | Yes | Certificate id. |
| `X-Change-Reason` | header | string | No | Reason of the change (1-500 UTF-8 characters, no control characters); defaults to the creation of the zone. 1 to 500 characters. |
| `dry_run` | query | string | No | `1`/`true` simulates the change: answers 200 with the plan (`Applied`) and saves nothing. `0`/`false` accepted; any other value: 400. One of `1`, `true`, `0`, `false`. |

#### Response

- `200`: The (revoked) certificate. Returns [OriginCert](https://developers.openwaf.dev/api/schemas/#origin-cert).

| Field | Type | Description |
|---|---|---|
| `id` | string (uuid) | Id. |
| `zone_id` | string (uuid) | Zone id. |
| `hostnames` | array of string |  |
| `key_type` | string | `csr`: the public key of a certificate request was certified. One of `ecdsa_p256`, `rsa2048`, `csr`. |
| `serial` | string | Lowercase hexadecimal without leading zeros. |
| `fingerprint_sha256` | string | SHA-256 fingerprint of the certificate. |
| `not_before` | string (date-time) | Start of validity. |
| `not_after` | string (date-time) | End of validity. |
| `validity_days` | integer |  |
| `status` | string | revoked takes precedence over expired. One of `active`, `revoked`, `expired`. |
| `created_by` | string | Who created it (actor). |
| `created_at` | string (date-time) | Creation time. |
| `revoked_at` | string (date-time) or null | Revocation time (null if active). |
| `revoked_by` | string or null | Who revoked it. |

#### Errors

| Status | Description |
|---|---|
| `403` | The token's role or limits do not allow it (`forbidden`). |
| `404` | Not found, or not visible to the token (`not_found`). |

#### Example

**curl**


```sh
curl -X DELETE "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/origin-certs/9e7c5a3b-1d2f-4e8a-b6c4-3a5d7f9b1c2e" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "X-Change-Reason: Server decommissioned"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.revokeOriginCert({
  zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
  cert: "9e7c5a3b-1d2f-4e8a-b6c4-3a5d7f9b1c2e"
}, {
  reason: "Server decommissioned"
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token from OPENWAF_TOKEN
result = owf.revoke_origin_cert(
    zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
    cert="9e7c5a3b-1d2f-4e8a-b6c4-3a5d7f9b1c2e",
    reason="Server decommissioned",
)
```



```json
{
  "id": "a5614527-0ce6-43be-ad1d-d012b7394867",
  "zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
  "hostnames": [
    "example.com",
    "*.example.com"
  ],
  "key_type": "ecdsa_p256",
  "serial": "5f3a9c0e7b21d4a68c13e9f02b7d4a61",
  "fingerprint_sha256": "example",
  "not_before": "2026-10-27T09:30:00Z",
  "not_after": "2026-10-27T09:30:00Z",
  "validity_days": 0,
  "status": "active",
  "created_by": "token:11111111-2222-4333-8444-555555555555",
  "created_at": "2026-10-27T09:30:00Z",
  "revoked_at": "2026-10-27T09:30:00Z",
  "revoked_by": null
}
```

## Get origin TLS status

```http
GET /v1/orgs/{org}/zones/{zone}/origin-tls/status
```

For each proxied name, returns its effective origin TLS settings, what a TLS handshake with the origin shows (without an HTTP request; private, loopback and link-local addresses are `skipped`) and the `525`/`526` errors reported by the edge in the last 24 hours. The result is cached for 60 seconds per zone.

**Required role:** `readonly` or higher.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `org` | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise `404`). |
| `zone` | path | string (uuid) | Yes | Zone id (see [List zones](https://developers.openwaf.dev/api/zones/#list-zones)). |

#### Response

- `200`: Returns [OriginTLSStatus](https://developers.openwaf.dev/api/schemas/#origin-tls-status).

| Field | Type | Description |
|---|---|---|
| `checked_at` | string (date-time) | Last check. |
| `hosts` | array of object |  |
| `hosts[].host` | string |  |
| `hosts[].upstream_url` | string | Without credentials. |
| `hosts[].tls_verify` | string | One of `full`, `full_strict`. |
| `hosts[].server_name` | string | Configured value (empty = the default). |
| `hosts[].client_cert` | boolean |  |
| `hosts[].effective_server_name` | string | SNI and verified name: server_name, or the origin host if it is a name, or the site's name if the origin is an IP. |
| `hosts[].probe` | object |  |
| `hosts[].edge_errors_24h` | integer | 525/526 events of the edge in the last 24 hours. |
| `hosts[].last_edge_error` | object or null |  |

#### Errors

| Status | Description |
|---|---|
| `404` | Not found, or not visible to the token (`not_found`). |

#### Example

**curl**


```sh
curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/origin-tls/status" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.getOriginTlsStatus({
  zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token from OPENWAF_TOKEN
result = owf.get_origin_tls_status(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d")
```



```json
{
  "checked_at": "2026-10-27T09:30:00Z",
  "hosts": [
    {
      "host": "www.example.com",
      "upstream_url": "https://203.0.113.10",
      "tls_verify": "full",
      "server_name": "",
      "client_cert": false,
      "effective_server_name": "example",
      "probe": {
        "state": "ok",
        "error": "",
        "issuer": "example",
        "subject": "example",
        "not_after": "2026-10-27T09:30:00Z",
        "days_left": 0,
        "name_match": false,
        "trusted_by": "public",
        "requests_client_cert": false
      },
      "edge_errors_24h": 0,
      "last_edge_error": {
        "ts": "2026-10-27T09:30:00Z",
        "status": 525,
        "msg": ""
      }
    }
  ]
}
```

## Download the Origin CA

```http
GET /v1/origin-tls/origin-ca.pem
```

Returns, in PEM, every non-expired OpenWAF Origin CA certificate (several during a rotation). In `full_strict` mode the edge trusts these certificates in addition to the public roots.

**Required role:** None: public, no token needed.

#### Response

- `200`: Body of type `text/plain`.

#### Errors

| Status | Description |
|---|---|
| `503` | `unavailable`: the Origin CA is not available right now. |

#### Example

**curl**


```sh
curl "https://dash.openwaf.dev/v1/origin-tls/origin-ca.pem"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.getOriginCa();
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token from OPENWAF_TOKEN
result = owf.get_origin_ca()
```



```text
-----BEGIN CERTIFICATE-----
MIIB…
-----END CERTIFICATE-----
```

## Download the edge client CA

```http
GET /v1/origin-tls/edge-client-ca.pem
```

Returns, in PEM, the CA of the client certificates that the edge presents to origins (mTLS). Configure it on an origin that requires the edge certificate.

**Required role:** None: public, no token needed.

#### Response

- `200`: Body of type `text/plain`.

#### Errors

| Status | Description |
|---|---|
| `503` | `unavailable`: the Origin CA is not available right now. |

#### Example

**curl**


```sh
curl "https://dash.openwaf.dev/v1/origin-tls/edge-client-ca.pem"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.getEdgeClientCa();
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token from OPENWAF_TOKEN
result = owf.get_edge_client_ca()
```



```text
-----BEGIN CERTIFICATE-----
MIIB…
-----END CERTIFICATE-----
```
