{
  "doc": {
    "id": "app/configure/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": "/llm/markdown/app/configure/client-certificates.md",
    "version": "e4f4d57da4cfedd520d3d465137d387fd54b532f",
    "updated_at": "2026-09-08T17:49:12.214Z",
    "headings": [
      {
        "id": "app/configure/client-certificates#client-certificates",
        "text": "Client Certificates",
        "level": 1
      },
      {
        "id": "app/configure/client-certificates#syntax",
        "text": "Syntax",
        "level": 2
      },
      {
        "id": "app/configure/client-certificates#usage",
        "text": "Usage",
        "level": 2
      },
      {
        "id": "app/configure/client-certificates#how-certificates-are-applied",
        "text": "How Certificates Are Applied",
        "level": 2
      },
      {
        "id": "app/configure/client-certificates#testing-with-multiple-client-identities",
        "text": "Testing with multiple client identities",
        "level": 3
      },
      {
        "id": "app/configure/client-certificates#testing-mtls-protected-endpoints",
        "text": "Testing mTLS-Protected Endpoints",
        "level": 2
      },
      {
        "id": "app/configure/client-certificates#visiting-an-mtls-endpoint",
        "text": "Visiting an mTLS endpoint",
        "level": 3
      },
      {
        "id": "app/configure/client-certificates#making-api-requests-to-an-mtls-endpoint",
        "text": "Making API requests to an mTLS endpoint",
        "level": 3
      },
      {
        "id": "app/configure/client-certificates#debugging-certificate-issues",
        "text": "Debugging Certificate Issues",
        "level": 2
      },
      {
        "id": "app/configure/client-certificates#inspecting-the-cypress-log",
        "text": "Inspecting the Cypress log",
        "level": 3
      },
      {
        "id": "app/configure/client-certificates#common-errors",
        "text": "Common errors",
        "level": 3
      },
      {
        "id": "app/configure/client-certificates#using-client-certificates-in-ci",
        "text": "Using Client Certificates in CI",
        "level": 2
      },
      {
        "id": "app/configure/client-certificates#storing-certificates-securely",
        "text": "Storing certificates securely",
        "level": 3
      },
      {
        "id": "app/configure/client-certificates#adding-the-certificates-directory-to-gitignore",
        "text": "Adding the certificates directory to .gitignore",
        "level": 3
      },
      {
        "id": "app/configure/client-certificates#history",
        "text": "History",
        "level": 2
      }
    ]
  },
  "chunks": [
    {
      "id": "app/configure/client-certificates#syntax",
      "doc_id": "app/configure/client-certificates",
      "heading": "Syntax",
      "heading_level": 2,
      "content_markdown": "## Syntax\n\n**clientCertificates _(Object\\[\\])_**\n\nAn array of objects defining the certificates. Each object must have the following properties\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `url` | `String` | URL to match requests against. Wildcards following [minimatch](https://github.com/isaacs/minimatch) rules are supported. |\n| `ca` | `Array` | _(Optional)_ Paths to one or more CA files to validate certs against, relative to project root. |\n| `certs` | `Object[]` | A PEM format certificate/private key pair or PFX certificate container |\n\nEach 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.\n\n**A PEM format certificate/private key pair can have the following properties:**\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `cert` | `String` | Path to the certificate file, relative to project root. |\n| `key` | `String` | Path to the private key file, relative to project root. |\n| `passphrase` | `String` | _(Optional)_ Path to a text file containing the passphrase, relative to project root. |\n\n**A PFX certificate container can have the following properties:**\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `pfx` | `String` | Path to the certificate container, relative to project root. |\n| `passphrase` | `String` | _(Optional)_ Path to a text file containing the passphrase, relative to project root. |\n",
      "section": "app",
      "anchors": [
        "syntax"
      ],
      "path": "/llm/json/chunked/app/configure/client-certificates.json",
      "token_estimate": 329
    },
    {
      "id": "app/configure/client-certificates#usage",
      "doc_id": "app/configure/client-certificates",
      "heading": "Usage",
      "heading_level": 2,
      "content_markdown": "## Usage\n\nTo configure CA / client certificates within your Cypress configuration, you can add the `clientCertificates` key to define an array of client certificates as shown below:\n\n*   cypress.config.js\n*   cypress.config.ts\n\n```\nconst { defineConfig } = require('cypress')\n\nmodule.exports = defineConfig({\n  clientCertificates: [\n    {\n      url: 'https://a.host.com',\n      ca: ['certs/ca.pem'],\n      certs: [\n        {\n          cert: 'certs/cert.pem',\n          key: 'certs/private.key',\n          passphrase: 'certs/pem-passphrase.txt',\n        },\n      ],\n    },\n    {\n      url: 'https://b.host.com/a_base_route/**',\n      ca: [],\n      certs: [\n        {\n          pfx: '/home/tester/certs/cert.pfx',\n          passphrase: '/home/tester/certs/pfx-passphrase.txt',\n        },\n      ],\n    },\n    {\n      url: 'https://a.host.*.com/',\n      ca: [],\n      certs: [\n        {\n          pfx: 'certs/cert.pfx',\n          passphrase: 'certs/pfx-passphrase.txt',\n        },\n      ],\n    },\n  ],\n})\n```\n\n```\nimport { defineConfig } from 'cypress'\n\nexport default defineConfig({\n  clientCertificates: [\n    {\n      url: 'https://a.host.com',\n      ca: ['certs/ca.pem'],\n      certs: [\n        {\n          cert: 'certs/cert.pem',\n          key: 'certs/private.key',\n          passphrase: 'certs/pem-passphrase.txt',\n        },\n      ],\n    },\n    {\n      url: 'https://b.host.com/a_base_route/**',\n      ca: [],\n      certs: [\n        {\n          pfx: '/home/tester/certs/cert.pfx',\n          passphrase: '/home/tester/certs/pfx-passphrase.txt',\n        },\n      ],\n    },\n    {\n      url: 'https://a.host.*.com/',\n      ca: [],\n      certs: [\n        {\n          pfx: 'certs/cert.pfx',\n          passphrase: 'certs/pfx-passphrase.txt',\n        },\n      ],\n    },\n  ],\n})\n```\n",
      "section": "app",
      "anchors": [
        "usage"
      ],
      "path": "/llm/json/chunked/app/configure/client-certificates.json",
      "token_estimate": 208
    },
    {
      "id": "app/configure/client-certificates#how-certificates-are-applied",
      "doc_id": "app/configure/client-certificates",
      "heading": "How Certificates Are Applied",
      "heading_level": 2,
      "content_markdown": "## How Certificates Are Applied\n\nCypress 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.**\n\nWhen 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.\n\nIf more than one entry matches the same URL, Cypress selects the entry with the **most specific (longest) path** pattern.\n\nThe `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.\n\n### Testing with multiple client identities\n\nCypress 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`.\n\nIf your tests need to authenticate as different users against the **same base URL**, you can work around this limitation in a couple of ways:\n\n*   **Distinct path patterns** – If your server exposes user-specific base paths, configure a separate `clientCertificates` entry for each path pattern:\n    \n    *   cypress.config.js\n    *   cypress.config.ts\n    \n    ```\n    const { defineConfig } = require('cypress')\n    \n    module.exports = defineConfig({\n      clientCertificates: [\n        {\n          url: 'https://example.com/users/alice/**',\n          certs: [\n            { pfx: 'certs/alice.pfx', passphrase: 'certs/alice-passphrase.txt' },\n          ],\n        },\n        {\n          url: 'https://example.com/users/bob/**',\n          certs: [{ pfx: 'certs/bob.pfx', passphrase: 'certs/bob-passphrase.txt' }],\n        },\n      ],\n    })\n    ```\n    \n    ```\n    import { defineConfig } from 'cypress'\n    \n    export default defineConfig({\n      clientCertificates: [\n        {\n          url: 'https://example.com/users/alice/**',\n          certs: [\n            { pfx: 'certs/alice.pfx', passphrase: 'certs/alice-passphrase.txt' },\n          ],\n        },\n        {\n          url: 'https://example.com/users/bob/**',\n          certs: [{ pfx: 'certs/bob.pfx', passphrase: 'certs/bob-passphrase.txt' }],\n        },\n      ],\n    })\n    ```\n    \n*   **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.\n",
      "section": "app",
      "anchors": [
        "how-certificates-are-applied"
      ],
      "path": "/llm/json/chunked/app/configure/client-certificates.json",
      "token_estimate": 407
    },
    {
      "id": "app/configure/client-certificates#testing-with-multiple-client-identities",
      "doc_id": "app/configure/client-certificates",
      "heading": "Testing with multiple client identities",
      "heading_level": 3,
      "content_markdown": "### Testing with multiple client identities\n\nCypress 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`.\n\nIf your tests need to authenticate as different users against the **same base URL**, you can work around this limitation in a couple of ways:\n\n*   **Distinct path patterns** – If your server exposes user-specific base paths, configure a separate `clientCertificates` entry for each path pattern:\n    \n    *   cypress.config.js\n    *   cypress.config.ts\n    \n    ```\n    const { defineConfig } = require('cypress')\n    \n    module.exports = defineConfig({\n      clientCertificates: [\n        {\n          url: 'https://example.com/users/alice/**',\n          certs: [\n            { pfx: 'certs/alice.pfx', passphrase: 'certs/alice-passphrase.txt' },\n          ],\n        },\n        {\n          url: 'https://example.com/users/bob/**',\n          certs: [{ pfx: 'certs/bob.pfx', passphrase: 'certs/bob-passphrase.txt' }],\n        },\n      ],\n    })\n    ```\n    \n    ```\n    import { defineConfig } from 'cypress'\n    \n    export default defineConfig({\n      clientCertificates: [\n        {\n          url: 'https://example.com/users/alice/**',\n          certs: [\n            { pfx: 'certs/alice.pfx', passphrase: 'certs/alice-passphrase.txt' },\n          ],\n        },\n        {\n          url: 'https://example.com/users/bob/**',\n          certs: [{ pfx: 'certs/bob.pfx', passphrase: 'certs/bob-passphrase.txt' }],\n        },\n      ],\n    })\n    ```\n    \n*   **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.\n",
      "section": "app",
      "anchors": [
        "testing-with-multiple-client-identities"
      ],
      "path": "/llm/json/chunked/app/configure/client-certificates.json",
      "token_estimate": 251
    },
    {
      "id": "app/configure/client-certificates#testing-mtls-protected-endpoints",
      "doc_id": "app/configure/client-certificates",
      "heading": "Testing mTLS-Protected Endpoints",
      "heading_level": 2,
      "content_markdown": "## Testing mTLS-Protected Endpoints\n\nOnce `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.\n\n### Visiting an mTLS endpoint\n\n```\n// cypress/e2e/mtls.cy.js\ndescribe('mTLS-protected app', () => {\n  it('loads the dashboard', () => {\n    // Cypress attaches the client cert automatically based on the URL\n    cy.visit('https://secure.example.com/dashboard')\n    cy.get('h1').should('contain', 'Welcome')\n  })\n})\n```\n\n### Making API requests to an mTLS endpoint\n\n[`cy.request()`](/llm/markdown/api/commands/request.md) also benefits from the configured certificates, making it straightforward to test REST APIs behind mTLS:\n\n```\ndescribe('mTLS API', () => {\n  it('returns 200 for an authenticated request', () => {\n    cy.request('GET', 'https://api.example.com/data').then((response) => {\n      expect(response.status).to.eq(200)\n    })\n  })\n\n  it('returns the expected payload', () => {\n    cy.request('POST', 'https://api.example.com/records', { name: 'test' })\n      .its('body.id')\n      .should('be.a', 'string')\n  })\n})\n```\n\nA `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/**`).\n",
      "section": "app",
      "anchors": [
        "testing-mtls-protected-endpoints"
      ],
      "path": "/llm/json/chunked/app/configure/client-certificates.json",
      "token_estimate": 259
    },
    {
      "id": "app/configure/client-certificates#visiting-an-mtls-endpoint",
      "doc_id": "app/configure/client-certificates",
      "heading": "Visiting an mTLS endpoint",
      "heading_level": 3,
      "content_markdown": "### Visiting an mTLS endpoint\n\n```\n// cypress/e2e/mtls.cy.js\ndescribe('mTLS-protected app', () => {\n  it('loads the dashboard', () => {\n    // Cypress attaches the client cert automatically based on the URL\n    cy.visit('https://secure.example.com/dashboard')\n    cy.get('h1').should('contain', 'Welcome')\n  })\n})\n```\n",
      "section": "app",
      "anchors": [
        "visiting-an-mtls-endpoint"
      ],
      "path": "/llm/json/chunked/app/configure/client-certificates.json",
      "token_estimate": 48
    },
    {
      "id": "app/configure/client-certificates#making-api-requests-to-an-mtls-endpoint",
      "doc_id": "app/configure/client-certificates",
      "heading": "Making API requests to an mTLS endpoint",
      "heading_level": 3,
      "content_markdown": "### Making API requests to an mTLS endpoint\n\n[`cy.request()`](/llm/markdown/api/commands/request.md) also benefits from the configured certificates, making it straightforward to test REST APIs behind mTLS:\n\n```\ndescribe('mTLS API', () => {\n  it('returns 200 for an authenticated request', () => {\n    cy.request('GET', 'https://api.example.com/data').then((response) => {\n      expect(response.status).to.eq(200)\n    })\n  })\n\n  it('returns the expected payload', () => {\n    cy.request('POST', 'https://api.example.com/records', { name: 'test' })\n      .its('body.id')\n      .should('be.a', 'string')\n  })\n})\n```\n\nA `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/**`).\n",
      "section": "app",
      "anchors": [
        "making-api-requests-to-an-mtls-endpoint"
      ],
      "path": "/llm/json/chunked/app/configure/client-certificates.json",
      "token_estimate": 165
    },
    {
      "id": "app/configure/client-certificates#debugging-certificate-issues",
      "doc_id": "app/configure/client-certificates",
      "heading": "Debugging Certificate Issues",
      "heading_level": 2,
      "content_markdown": "## Debugging Certificate Issues\n\n### Inspecting the Cypress log\n\nWhen 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.\n\n### Common errors\n\n| Symptom | Likely cause |\n| --- | --- |\n| `400 Bad Request` from server | Certificate not presented — URL pattern mismatch |\n| `SSL_ERROR_HANDSHAKE_FAILURE` | Wrong certificate, expired cert, or CA chain mismatch |\n| `ENOENT: no such file or directory` | Certificate file path is incorrect or relative path is wrong |\n| `bad decrypt` / `wrong final block length` | Passphrase file content is incorrect or has trailing whitespace |\n| Certificate works locally but not in CI | Certificate files not committed or not present at the expected path in CI |\n",
      "section": "app",
      "anchors": [
        "debugging-certificate-issues"
      ],
      "path": "/llm/json/chunked/app/configure/client-certificates.json",
      "token_estimate": 207
    },
    {
      "id": "app/configure/client-certificates#inspecting-the-cypress-log",
      "doc_id": "app/configure/client-certificates",
      "heading": "Inspecting the Cypress log",
      "heading_level": 3,
      "content_markdown": "### Inspecting the Cypress log\n\nWhen 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.\n",
      "section": "app",
      "anchors": [
        "inspecting-the-cypress-log"
      ],
      "path": "/llm/json/chunked/app/configure/client-certificates.json",
      "token_estimate": 65
    },
    {
      "id": "app/configure/client-certificates#common-errors",
      "doc_id": "app/configure/client-certificates",
      "heading": "Common errors",
      "heading_level": 3,
      "content_markdown": "### Common errors\n\n| Symptom | Likely cause |\n| --- | --- |\n| `400 Bad Request` from server | Certificate not presented — URL pattern mismatch |\n| `SSL_ERROR_HANDSHAKE_FAILURE` | Wrong certificate, expired cert, or CA chain mismatch |\n| `ENOENT: no such file or directory` | Certificate file path is incorrect or relative path is wrong |\n| `bad decrypt` / `wrong final block length` | Passphrase file content is incorrect or has trailing whitespace |\n| Certificate works locally but not in CI | Certificate files not committed or not present at the expected path in CI |\n",
      "section": "app",
      "anchors": [
        "common-errors"
      ],
      "path": "/llm/json/chunked/app/configure/client-certificates.json",
      "token_estimate": 136
    },
    {
      "id": "app/configure/client-certificates#using-client-certificates-in-ci",
      "doc_id": "app/configure/client-certificates",
      "heading": "Using Client Certificates in CI",
      "heading_level": 2,
      "content_markdown": "## Using Client Certificates in CI\n\n### Storing certificates securely\n\nNever 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.\n\n**GitHub Actions example:**\n\n```\n- name: Write client certificates\n  run: |\n    mkdir -p certs\n    echo \"${{ secrets.CLIENT_CERT }}\"       | base64 --decode > certs/cert.pem\n    echo \"${{ secrets.CLIENT_KEY }}\"        | base64 --decode > certs/private.key\n    echo \"${{ secrets.CLIENT_PASSPHRASE }}\" > certs/pem-passphrase.txt\n```\n\nThen reference those paths in `cypress.config.ts`:\n\n```\nclientCertificates: [\n  {\n    url: 'https://secure.example.com/**',\n    certs: [\n      {\n        cert: 'certs/cert.pem',\n        key: 'certs/private.key',\n        passphrase: 'certs/pem-passphrase.txt',\n      },\n    ],\n  },\n]\n```\n\n### Adding the certificates directory to `.gitignore`\n\n```\n# .gitignore\ncerts/\n```\n\nMake sure the `certs/` directory (or whichever path you use) is in `.gitignore` so that private keys are never accidentally committed.\n",
      "section": "app",
      "anchors": [
        "using-client-certificates-in-ci"
      ],
      "path": "/llm/json/chunked/app/configure/client-certificates.json",
      "token_estimate": 179
    },
    {
      "id": "app/configure/client-certificates#storing-certificates-securely",
      "doc_id": "app/configure/client-certificates",
      "heading": "Storing certificates securely",
      "heading_level": 3,
      "content_markdown": "### Storing certificates securely\n\nNever 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.\n\n**GitHub Actions example:**\n\n```\n- name: Write client certificates\n  run: |\n    mkdir -p certs\n    echo \"${{ secrets.CLIENT_CERT }}\"       | base64 --decode > certs/cert.pem\n    echo \"${{ secrets.CLIENT_KEY }}\"        | base64 --decode > certs/private.key\n    echo \"${{ secrets.CLIENT_PASSPHRASE }}\" > certs/pem-passphrase.txt\n```\n\nThen reference those paths in `cypress.config.ts`:\n\n```\nclientCertificates: [\n  {\n    url: 'https://secure.example.com/**',\n    certs: [\n      {\n        cert: 'certs/cert.pem',\n        key: 'certs/private.key',\n        passphrase: 'certs/pem-passphrase.txt',\n      },\n    ],\n  },\n]\n```\n",
      "section": "app",
      "anchors": [
        "storing-certificates-securely"
      ],
      "path": "/llm/json/chunked/app/configure/client-certificates.json",
      "token_estimate": 127
    },
    {
      "id": "app/configure/client-certificates#adding-the-certificates-directory-to-gitignore",
      "doc_id": "app/configure/client-certificates",
      "heading": "Adding the certificates directory to .gitignore",
      "heading_level": 3,
      "content_markdown": "### Adding the certificates directory to `.gitignore`\n\n```\n# .gitignore\ncerts/\n```\n\nMake sure the `certs/` directory (or whichever path you use) is in `.gitignore` so that private keys are never accidentally committed.\n",
      "section": "app",
      "anchors": [
        "adding-the-certificates-directory-to-gitignore"
      ],
      "path": "/llm/json/chunked/app/configure/client-certificates.json",
      "token_estimate": 44
    },
    {
      "id": "app/configure/client-certificates#history",
      "doc_id": "app/configure/client-certificates",
      "heading": "History",
      "heading_level": 2,
      "content_markdown": "## History\n\n| Version | Changes |\n| --- | --- |\n| [15.16.0](/llm/markdown/app/references/changelog.md#15-16-0) | Added support for ECDSA (EC) keys in PEM and PFX client certificates |\n| [8.0.0](/llm/markdown/app/references/changelog.md#8-0-0) | Added Client Certificates configuration options |\n",
      "section": "app",
      "anchors": [
        "history"
      ],
      "path": "/llm/json/chunked/app/configure/client-certificates.json",
      "token_estimate": 49
    }
  ]
}