---
id: app/references/client-certificates
title: Configure client certificates in Cypress
description: >-
  Configure certificate authority (CA) and client certificates to test mTLS
  (mutual TLS) protected endpoints in Cypress on a per-URL basis.
section: app
source_path: docs/app/references/client-certificates.mdx
version: 46bc6c4400c4ef8fd2acd13eeb18785cdc2402a7
updated_at: '2026-08-24T13:11:09.044Z'
---
# Client Certificates

Many enterprise, government, and regulated-industry systems require clients to present a certificate to authenticate at the TLS layer — before your application code even runs. This pattern is called **mutual TLS (mTLS)**: unlike standard HTTPS where only the server proves its identity, mTLS requires _both_ sides to present certificates, so the server can verify the caller is a trusted client.

Without mTLS support, you face an unpleasant choice: disable certificate requirements in your test environment (meaning your tests run against a system that doesn't match production), or rely on slow and inconsistent manual testing.

The `clientCertificates` configuration lets you supply the certificates Cypress needs to authenticate against mTLS-protected endpoints, so your automated E2E tests exercise the real security boundary. Misconfigurations, expired certificates, or changed server requirements get caught in CI — not in production, where the cost is highest.

**Document Scope**

This document covers how to configure certificate file paths for use in your tests. The creation and management of certificate files themselves are outside the scope of Cypress documentation.

## Syntax

**clientCertificates _(Object\[\])_**

An array of objects defining the certificates. Each object must have the following properties

| Property | Type | Description |
| --- | --- | --- |
| `url` | `String` | URL to match requests against. Wildcards following [minimatch](https://github.com/isaacs/minimatch) rules are supported. |
| `ca` | `Array` | _(Optional)_ Paths to one or more CA files to validate certs against, relative to project root. |
| `certs` | `Object[]` | A PEM format certificate/private key pair or PFX certificate container |

Each object in the `certs` array can define either a **PEM format certificate/private key pair** or a **PFX certificate container**. Both **RSA** and **ECDSA (EC)** keys are supported.

**A PEM format certificate/private key pair can have the following properties:**

| Property | Type | Description |
| --- | --- | --- |
| `cert` | `String` | Path to the certificate file, relative to project root. |
| `key` | `String` | Path to the private key file, relative to project root. |
| `passphrase` | `String` | _(Optional)_ Path to a text file containing the passphrase, relative to project root. |

**A PFX certificate container can have the following properties:**

| Property | Type | Description |
| --- | --- | --- |
| `pfx` | `String` | Path to the certificate container, relative to project root. |
| `passphrase` | `String` | _(Optional)_ Path to a text file containing the passphrase, relative to project root. |

## Usage

To configure CA / client certificates within your Cypress configuration, you can add the `clientCertificates` key to define an array of client certificates as shown below:

*   cypress.config.js
*   cypress.config.ts

```
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  clientCertificates: [
    {
      url: 'https://a.host.com',
      ca: ['certs/ca.pem'],
      certs: [
        {
          cert: 'certs/cert.pem',
          key: 'certs/private.key',
          passphrase: 'certs/pem-passphrase.txt',
        },
      ],
    },
    {
      url: 'https://b.host.com/a_base_route/**',
      ca: [],
      certs: [
        {
          pfx: '/home/tester/certs/cert.pfx',
          passphrase: '/home/tester/certs/pfx-passphrase.txt',
        },
      ],
    },
    {
      url: 'https://a.host.*.com/',
      ca: [],
      certs: [
        {
          pfx: 'certs/cert.pfx',
          passphrase: 'certs/pfx-passphrase.txt',
        },
      ],
    },
  ],
})
```

```
import { defineConfig } from 'cypress'

export default defineConfig({
  clientCertificates: [
    {
      url: 'https://a.host.com',
      ca: ['certs/ca.pem'],
      certs: [
        {
          cert: 'certs/cert.pem',
          key: 'certs/private.key',
          passphrase: 'certs/pem-passphrase.txt',
        },
      ],
    },
    {
      url: 'https://b.host.com/a_base_route/**',
      ca: [],
      certs: [
        {
          pfx: '/home/tester/certs/cert.pfx',
          passphrase: '/home/tester/certs/pfx-passphrase.txt',
        },
      ],
    },
    {
      url: 'https://a.host.*.com/',
      ca: [],
      certs: [
        {
          pfx: 'certs/cert.pfx',
          passphrase: 'certs/pfx-passphrase.txt',
        },
      ],
    },
  ],
})
```

## How Certificates Are Applied

Cypress automatically applies the correct client certificate for every outgoing network request — including those initiated by [`cy.visit()`](/llm/markdown/api/commands/visit.md) and [`cy.request()`](/llm/markdown/api/commands/request.md) — based on URL pattern matching. **No additional options need to be passed to these commands.**

When Cypress makes a request, it compares the target URL against each `url` pattern in the `clientCertificates` array. If a matching entry is found, its certificates are attached to the request automatically.

If more than one entry matches the same URL, Cypress selects the entry with the **most specific (longest) path** pattern.

The `url` field in each `clientCertificates` entry supports [minimatch](https://github.com/isaacs/minimatch) glob patterns (for example `https://a.host.com/api/**`), so a single entry can cover an entire path hierarchy.

### Testing with multiple client identities

Cypress does not support passing a specific certificate to `cy.visit()` or any other command at call time. Certificate selection is always URL-pattern-based and configured statically in `clientCertificates`.

If your tests need to authenticate as different users against the **same base URL**, you can work around this limitation in a couple of ways:

*   **Distinct path patterns** – If your server exposes user-specific base paths, configure a separate `clientCertificates` entry for each path pattern:
    
    *   cypress.config.js
    *   cypress.config.ts
    
    ```
    const { defineConfig } = require('cypress')
    
    module.exports = defineConfig({
      clientCertificates: [
        {
          url: 'https://example.com/users/alice/**',
          certs: [
            { pfx: 'certs/alice.pfx', passphrase: 'certs/alice-passphrase.txt' },
          ],
        },
        {
          url: 'https://example.com/users/bob/**',
          certs: [{ pfx: 'certs/bob.pfx', passphrase: 'certs/bob-passphrase.txt' }],
        },
      ],
    })
    ```
    
    ```
    import { defineConfig } from 'cypress'
    
    export default defineConfig({
      clientCertificates: [
        {
          url: 'https://example.com/users/alice/**',
          certs: [
            { pfx: 'certs/alice.pfx', passphrase: 'certs/alice-passphrase.txt' },
          ],
        },
        {
          url: 'https://example.com/users/bob/**',
          certs: [{ pfx: 'certs/bob.pfx', passphrase: 'certs/bob-passphrase.txt' }],
        },
      ],
    })
    ```
    
*   **Separate configuration files** – Maintain separate Cypress configuration files (for example `cypress.alice.config.ts` and `cypress.bob.config.ts`), each specifying the appropriate `clientCertificates`, and run them as separate test suites.
    

## Testing mTLS-Protected Endpoints

Once `clientCertificates` is configured, Cypress attaches the correct certificate automatically. Your test code looks the same as any other E2E test — no special options are required on individual commands.

### Visiting an mTLS endpoint

```
// cypress/e2e/mtls.cy.js
describe('mTLS-protected app', () => {
  it('loads the dashboard', () => {
    // Cypress attaches the client cert automatically based on the URL
    cy.visit('https://secure.example.com/dashboard')
    cy.get('h1').should('contain', 'Welcome')
  })
})
```

### Making API requests to an mTLS endpoint

[`cy.request()`](/llm/markdown/api/commands/request.md) also benefits from the configured certificates, making it straightforward to test REST APIs behind mTLS:

```
describe('mTLS API', () => {
  it('returns 200 for an authenticated request', () => {
    cy.request('GET', 'https://api.example.com/data').then((response) => {
      expect(response.status).to.eq(200)
    })
  })

  it('returns the expected payload', () => {
    cy.request('POST', 'https://api.example.com/records', { name: 'test' })
      .its('body.id')
      .should('be.a', 'string')
  })
})
```

A `400 Bad Request` response (or a TLS handshake error in the Cypress log) when hitting an endpoint is the most common sign that the client certificate was not presented. Double-check that the `url` pattern in `clientCertificates` matches the full URL of your target — including any port number if the server runs on a non-standard port (e.g. `https://secure.example.com:8443/**`).

## Debugging Certificate Issues

### Inspecting the Cypress log

When Cypress makes a request to a URL that matches a `clientCertificates` entry, it logs `clientCertificates` details in the **Cypress App** network panel. Open the panel and look for the lock icon next to the matching request to confirm that a certificate was attached.

### Common errors

| Symptom | Likely cause |
| --- | --- |
| `400 Bad Request` from server | Certificate not presented — URL pattern mismatch |
| `SSL_ERROR_HANDSHAKE_FAILURE` | Wrong certificate, expired cert, or CA chain mismatch |
| `ENOENT: no such file or directory` | Certificate file path is incorrect or relative path is wrong |
| `bad decrypt` / `wrong final block length` | Passphrase file content is incorrect or has trailing whitespace |
| Certificate works locally but not in CI | Certificate files not committed or not present at the expected path in CI |

## Using Client Certificates in CI

### Storing certificates securely

Never commit unencrypted private keys to your repository. Instead, store certificate material as **CI secrets** and write them to disk at the start of your pipeline.

**GitHub Actions example:**

```
- name: Write client certificates
  run: |
    mkdir -p certs
    echo "${{ secrets.CLIENT_CERT }}"       | base64 --decode > certs/cert.pem
    echo "${{ secrets.CLIENT_KEY }}"        | base64 --decode > certs/private.key
    echo "${{ secrets.CLIENT_PASSPHRASE }}" > certs/pem-passphrase.txt
```

Then reference those paths in `cypress.config.ts`:

```
clientCertificates: [
  {
    url: 'https://secure.example.com/**',
    certs: [
      {
        cert: 'certs/cert.pem',
        key: 'certs/private.key',
        passphrase: 'certs/pem-passphrase.txt',
      },
    ],
  },
]
```

### Adding the certificates directory to `.gitignore`

```
# .gitignore
certs/
```

Make sure the `certs/` directory (or whichever path you use) is in `.gitignore` so that private keys are never accidentally committed.

## History

| Version | Changes |
| --- | --- |
| [15.16.0](/llm/markdown/app/references/changelog.md#15-16-0) | Added support for ECDSA (EC) keys in PEM and PFX client certificates |
| [8.0.0](/llm/markdown/app/references/changelog.md#8-0-0) | Added Client Certificates configuration options |
