> ## Documentation Index
> Fetch the complete documentation index at: https://docs.streamnative.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Configure mTLS authentication

Use mutual TLS (mTLS) to authenticate clients with certificates. The broker validates the certificate chain and uses the client certificate's common name (CN) as its Pulsar role.

## Before you begin

* Upgrade to StreamNative Operator Helm chart **0.20.6** (Operator **v0.20.18**) and update its CRDs. This release preserves `clientAuth` when server authentication is configured through `custom` instead of the typed `authentication` field. See [Upgrade StreamNative Operator](/private-cloud/v2/operate-private-cloud/upgrade/upgrade-sn-operator).
* Prepare a CA, a server certificate and private key, and a separate client certificate and private key for the broker's internal client.
* Check that the CA, server certificate, and client certificates are within their validity periods. Renew expired certificates and update their Secrets before testing connections.
* Include the Service and advertised broker DNS names in the server certificate's subject alternative names (SANs). Include `serverAuth` usage on the server certificate and `clientAuth` usage on client certificates.
* Choose the client certificate common names and grant their corresponding roles the required permissions.

The examples use Private Cloud Pulsar 4.2.1.17 with the API key authorization provider. They assume an existing broker deployment with storage and networking already configured.

## Create certificate Secrets

Create two Secrets in the broker's namespace. Each Secret contains `tls.crt`, `tls.key`, and `ca.crt`. Supply the leaf certificate and any required intermediate certificates in `tls.crt`.

```bash theme={null}
kubectl -n pulsar create secret generic pulsar-server-tls \
  --from-file=tls.crt=server.crt \
  --from-file=tls.key=server.key \
  --from-file=ca.crt=ca.crt

kubectl -n pulsar create secret generic pulsar-client-mtls \
  --from-file=tls.crt=broker-admin.crt \
  --from-file=tls.key=broker-admin.key \
  --from-file=ca.crt=ca.crt
```

The examples assume the internal client's CN is `broker-admin`. Give each application its own client certificate and role. Distribute the trusted CA, not its signing private key, to components and applications.

## Configure the server and its internal client

Merge these settings into your `PulsarBroker`:

```yaml theme={null}
spec:
  tls:
    enabled: true
    certSecretName: pulsar-server-tls
    trustCertsEnabled: true
  config:
    authentication:
      mtls:
        enabled: true
    clientAuth:
      mtls:
        certSecretName: pulsar-client-mtls
    authorization:
      superUserRoles: broker-admin
    custom:
      PULSAR_PREFIX_authorizationProvider: io.streamnative.pulsar.broker.authorization.AuthorizationProviderApiKeys
```

| Field | Purpose |
| - | - |
| `spec.tls` | Enables the server TLS endpoints and mounts the server certificate, private key, and trusted CA. |
| `spec.config.authentication.mtls.enabled` | Enables certificate authentication for incoming TLS connections. |
| `spec.config.clientAuth.mtls.certSecretName` | Provides credentials and the trusted CA for the broker's own Pulsar client. |
| `spec.config.authorization.superUserRoles` | Gives the listed certificate common names administrative permissions. |

Configure `spec.tls` before, or in the same update as, enabling `clientAuth.mtls` or custom broker-client TLS properties. Client-side TLS settings alone do not create server TLS endpoints.

The Operator mounts server material under `/etc/tls/pulsar-broker` and client material under `/mnt/client/mtls`. The typed client configuration generates:

```text theme={null}
brokerClientAuthenticationPlugin=org.apache.pulsar.client.impl.auth.AuthenticationTls
brokerClientAuthenticationParameters=tlsCertFile:/mnt/client/mtls/tls.crt,tlsKeyFile:/mnt/client/mtls/tls.key
brokerClientTlsEnabled=true
brokerClientTrustCertsFilePath=/mnt/client/mtls/ca.crt
```

The generated ConfigMap uses the `PULSAR_PREFIX_` form of these keys. A mounted certificate Secret alone does not prove that the internal client's authentication properties were generated correctly.

If you configure server authentication through `spec.config.custom`, you can omit the typed `authentication` field and retain typed `clientAuth.mtls` on Operator 0.20.16. Custom authentication properties take precedence over typed values. See [Authentication overview](/private-cloud/v2/configure-private-cloud/security/authentication/private-cloud-authentication-overview).

For a proxy or Functions Worker, `clientAuth.mtls` configures its connection to brokers. Configure the TLS broker endpoint and the certificate role's permissions for that component. Do not assume that enabling TLS on the component's own server also changes its broker connection URL.

## Connect a client

Use the actual TLS endpoints. The default broker ports are 6651 for `pulsar+ssl://` and 8443 for HTTPS. Operator-generated non-TLS `brokerServiceUrl` and `webServiceUrl` values must not be reused as TLS client URLs.

With the default `cluster_tls` listener, produce messages as follows:

```bash theme={null}
bin/pulsar-client \
  --url pulsar+ssl://<broker-service>:6651 \
  --listener-name cluster_tls \
  --tlsTrustCertsFilePath /path/to/ca.crt \
  --auth-plugin org.apache.pulsar.client.impl.auth.AuthenticationTls \
  --auth-params '{"tlsCertFile":"/path/to/client.crt","tlsKeyFile":"/path/to/client.key"}' \
  produce persistent://<tenant>/<namespace>/mtls-check -m hello -n 3
```

Start a consumer first in another terminal using the same connection options and `consume persistent://<tenant>/<namespace>/mtls-check -s mtls-check -n 3`. Use the listener name configured by your deployment if it differs from `cluster_tls`.

For a Java application, enable CA and hostname verification:

```java theme={null}
PulsarClient client = PulsarClient.builder()
    .serviceUrl("pulsar+ssl://<broker-service>:6651")
    .listenerName("cluster_tls")
    .tlsTrustCertsFilePath("/path/to/ca.crt")
    .allowTlsInsecureConnection(false)
    .enableTlsHostnameVerification(true)
    .authentication(AuthenticationFactory.TLS(
        "/path/to/client.crt", "/path/to/client.key"))
    .build();
```

Import `PulsarClient` and `AuthenticationFactory` from `org.apache.pulsar.client.api`, and close the client when the application exits.

Check an administrative HTTPS endpoint with the client certificate:

```bash theme={null}
curl --fail --show-error \
  --cacert ca.crt --cert broker-admin.crt --key broker-admin.key \
  https://<broker-service>:8443/admin/v2/brokers/<pulsar-cluster-name>
```

For an authorized administrative CN, expect HTTP 200. The certificate's SANs must match the hostname in the URL.

## Verify authentication and internal operations

Check all of the following after the broker becomes Ready:

| Test | Expected result |
| - | - |
| Authorized certificate, matching server hostname and CA | Protected operation succeeds. |
| Trusted certificate whose CN lacks the required permissions | The request reaches authorization and is rejected. |
| No client certificate | TLS handshake is rejected. |
| Certificate signed by a CA outside the trusted certificate chain | TLS handshake is rejected, even if its CN matches an administrator. |
| Produce and consume messages | Message count and content match. |
| Update and read a topic retention policy | Both operations succeed, exercising the broker's system-topic client. |

For example, use an administrative `pulsar-admin` client with HTTPS, CA, and TLS authentication settings to set and retrieve a policy on an existing test topic:

```bash theme={null}
bin/pulsar-admin \
  --admin-url https://<broker-service>:8443 \
  --tls-trust-cert-path /path/to/ca.crt \
  --auth-plugin org.apache.pulsar.client.impl.auth.AuthenticationTls \
  --auth-params '{"tlsCertFile":"/path/to/client.crt","tlsKeyFile":"/path/to/client.key"}' \
  topics set-retention persistent://<tenant>/<namespace>/mtls-check --time 10m --size -1
```

Use the same connection options with `topics get-retention persistent://<tenant>/<namespace>/mtls-check` to confirm the value. This example retains messages for 10 minutes without a size limit. If you set a size limit, make it larger than the namespace's backlog quota. A lower retention limit can cause HTTP 412.

If Pods are Ready but topic policies fail, inspect broker logs and generated client properties. Enabling TLS while connecting to `pulsar://...:6650` can cause handshake failures; depending on the client path, a configuration error can also appear. Verify the URL scheme, port, listener, CA, hostname, and internal client role together.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.