mTLS
Client certificates are a popular way of adding an extra layer of security to your client authentication. It can either be added on top or instead of the regular authentication provided by the role based authentication using username and password. While connecting, a client identifies using a client certificate. The broker has stored a client certificate authority and allows a connection, if the client certificate gets validated, the connection is allowed.
The CA Management allows the upload of client CAs to the broker via a UI.
Listener configuration
MTLS has to be activated on a port (listener) level.
Default Cloud configuration
The default broker configuration, when mTLS is selected in a cloud subscription for a port looks like this (shown with port 8883):
# Listener for the application, MQTTS over TCP with client certs
listener 8883
certfile /mosquitto/config/certs/server.pem
keyfile /mosquitto/config/certs/server.key
capath /mosquitto/config/capath_mqtts
require_certificate true
enable_control_api true
plugin /usr/lib/cedalo_certificate_management.so
If you need any changes to this default please contact our support team.
On-premises configuration
The following options can be used for mTLS configuration.
Certificate and Key Settings (Server-side)
cafile <path>: Specifies the path to the Certificate Authority (CA) file that the broker will use to validate client certificates.capath <path>: Specifies the path to the Certificate Authority (CA) folder that the broker will use to validate client certificates. This is needed if more then one file is present.certfile <path>: The server's public certificate file that will be presented to clients during the TLS handshake.keyfile <path>: The server's private key file used to establish the TLS connection. The private key should remain secure and not be shared.
The path of the capath configuration should contain at least a root CA cert.
Note: The cafile configuration with a single PEM file containing the CA
chain is currently not supported for this command.
Certificate and Key Settings (Client-side validation)
require_certificate <true|false>: Determines whether clients are required to present a valid certificate for authentication. Set this totrueto enforce mTLS.tls_version <version>: Specifies the minimum TLS version allowed for the connection. Common values includetlsv1.2ortlsv1.3to ensure a secure connection.use_identity_as_username <true|false>: If set totrue, the broker will use the client certificate's Common Name (CN) field as the client's username for authentication.
Plugin configuration
To enable the plugin it must be loaded into the broker with, by adding the
following to your mosquitto.conf:
plugin /usr/lib/cedalo_certificate_management.so
Control API
In addition to enable the brokers internal $CONTROL/broker/v1 API we need to
add:
enable_control_api true
This is required to determine the listeners identifier, where the changes should be applied to.
This plugin provides a Mosquitto control API which manages client CA certificates for
certificate based authentication/validation.
The topic of the plugin's control API is $CONTROL/certificate-management/v1.
Currently, supported commands offered by the API are insertCACertificate, which
can be used to extend the existing Certificate Authority (CA) Chain or just the
Root CA by an additional signing/validating certificate. To get rid of a
previously added CA cert, the deleteCACertificate command can be used.
Mutual TLS (mTLS) on Kubernetes and OpenShift
This guide matches the 3.2 Helm charts in this repository.
| Platform | Single node | HA cluster (HAProxy in front of MQTT) |
|---|---|---|
| Kubernetes | mosquitto-3.2-platform-3.2-kubernetes-sn | mosquitto-3.2-platform-3.2-kubernetes-cluster |
| OpenShift | mosquitto-3.2-platform-3.2-openshift-sn | mosquitto-3.2-platform-3.2-openshift-cluster |
Use kubectl on Kubernetes and oc on OpenShift unless noted otherwise.
How this chart’s TLS modes differ (short)
The charts support three different MQTT TLS stories. This document covers only mosquitto.mtls. For install values, ports, and secrets for the other two, see serverOnlyTLS.md.
| Mode | Helm switch (MQTT) | Who terminates TLS for clients | Client certificate |
|---|---|---|---|
| HAProxy TLS | haproxy.tls.enabled | HAProxy | Not validated by Mosquitto on the MQTT path (HAProxy presents the server cert). |
| Server-only TLS on Mosquitto | mosquitto.serverOnlyTls.enabled | Mosquitto | Optional (one-way TLS: server proves identity to the client). |
| mTLS on Mosquitto | mosquitto.mtls.enabled | Mosquitto | Required when requireCertificate is true: broker validates clients against capath. |
Do not enable mosquitto.mtls and mosquitto.serverOnlyTls together. Both render a secure listener on mosquitto.ports.secureListenerTarget (default 8883); use exactly one broker-side TLS mode.
What mosquitto.mtls does
When mosquitto.mtls.enabled is true, the chart configures Mosquitto with:
- A server certificate and key (your broker identity).
- A
capathof client CA material so Mosquitto can validate client certificates. - Optional
require_certificate,use_identity_as_username, andtls_version(see parameters below).
On HA deployments, HAProxy exposes 8883 as TCP passthrough to the same broker port (it does not decrypt MQTT). Clients still perform TLS with Mosquitto; HAProxy only forwards bytes.
HAProxy PROXY protocol: with mosquitto.mtls.enabled, set haproxy.proxyProtocol.enabled to false. The chart fails template validation if proxy protocol stays enabled while mTLS (or serverOnlyTls) is on (templates/mosquitto-config.yaml).
Prerequisites: files and secrets
Server identity (broker)
Typical files (default Secret key names in the chart: server.pem, server.key):
server.pem— Server certificate (PEM; may include chain).server.key— Matching private key.
Client trust store (who may connect as a client)
Mosquitto uses a capath: a directory of CA certificates with OpenSSL-style hash symlinks (files named like 4b073644.0). The chart expects a second Secret that is always created outside Helm (chart does not template it):
ca.crt— Human-readable CA (also copied intocapathby the init logic).<hash>.0— Same CA (or additional CAs), named with the subject hash so Mosquitto can look up trust anchors.
Compute the hash name (use -subject_hash_old on OpenSSL 3 if your Mosquitto/OpenSSL combo expects the legacy hash):
HASH=$(openssl x509 -subject_hash_old -noout -in ca.crt 2>/dev/null || openssl x509 -hash -noout -in ca.crt)
cp ca.crt "${HASH}.0"
Issue client certificates from this CA (or a CA trusted under that capath) for MQTT clients.
Create Kubernetes / OpenShift Secrets
Set your namespace:
NAMESPACE="your-namespace"
1) Server cert Secret (name must match mosquitto.mtls.secretName, default mosquitto-mtls-server-tls):
kubectl create secret generic mosquitto-mtls-server-tls \
-n "$NAMESPACE" \
--from-file=server.pem=./server.pem \
--from-file=server.key=./server.key
2) Client CA Secret (name must match mosquitto.mtls.clientCaSecretName, default mosquitto-client-ca). This Secret cannot be created by Helm for mTLS:
kubectl create secret generic mosquitto-client-ca \
-n "$NAMESPACE" \
--from-file=ca.crt=./ca.crt \
--from-file=${HASH}.0=./${HASH}.0
For OpenShift, use oc create secret generic with the same arguments.
When using externally created server material, set mosquitto.mtls.createSecret=false. With validateSecrets: true (chart default), both Secrets must exist before helm install / helm upgrade.
How certificates are mounted (defaults)
Values are configurable; defaults match mosquitto-3.2-platform-3.2-kubernetes-cluster values.yaml.
| Role | Secret | Mount directory in Mosquitto | Files (defaults) |
|---|---|---|---|
| Server | mosquitto.mtls.secretName | mosquitto.mtls.tlsMountPath (/mosquitto/config/certs) | server.pem, server.key |
| Client CAs | mosquitto.mtls.clientCaSecretName | mosquitto.mtls.caMountPath (/mosquitto/config/capath_mqtts) | ca.crt, <hash>.0, … |
An init container copies CA files from the Secret mount into the capath volume and fixes permissions so Mosquitto can read capath (templates/statefulset.yaml).
mosquitto.mtls configuration parameters
Server certificate (Helm-managed or external)
mosquitto.mtls.enabled— Turn on the mTLS listener onmosquitto.ports.secureListenerTarget(default 8883). Default:false.mosquitto.mtls.secretName— Secret holdingserver.pem/server.key(or yourcertFile/keyFilenames). Default:mosquitto-mtls-server-tls.mosquitto.mtls.createSecret—true: Helm creates the server Secret fromcertContent/keyContent.false: create the server Secret withkubectl/oc(recommended for ops). Default:true.mosquitto.mtls.certFile/mosquitto.mtls.keyFile— Keys inside the server Secret. Defaults:server.pem,server.key.mosquitto.mtls.tlsMountPath— Directory only where server PEM/key are mounted. Default:/mosquitto/config/certs.mosquitto.mtls.certName/mosquitto.mtls.keyName— Filenames used inmosquitto.conf(certfile/keyfile). Defaults:server.pem,server.key.mosquitto.mtls.certContent/mosquitto.mtls.keyContent— Base64; only whencreateSecret: true.
Client CA (always external Secret)
mosquitto.mtls.clientCaSecretName— Secret withca.crtand hash-named files. Default:mosquitto-client-ca. Helm does not create this Secret.mosquitto.mtls.caMountPath— Directory used as Mosquittocapath. Default:/mosquitto/config/capath_mqtts.
Mosquitto TLS behavior
mosquitto.mtls.requireCertificate— Maps torequire_certificate.true: clients must present a cert the broker trusts. Default:true.mosquitto.mtls.useIdentityAsUsername— Whentrue, emitsuse_identity_as_username trueso the cert identity can drive username semantics. Default:false.mosquitto.mtls.tlsVersion— When non-empty, setstls_version(e.g.tlsv1.2,tlsv1.3). Default:""(Mosquitto default).
Port (shared with other secure modes)
mosquitto.ports.secureListenerTarget— Broker listener port for mTLS. Default:8883.
Rendered mosquitto.conf (conceptual)
With mTLS enabled, the chart adds a listener similar to:
listener <secureListenerTarget>
protocol mqtt
certfile <tlsMountPath>/<certName>
keyfile <tlsMountPath>/<keyName>
capath <caMountPath>
require_certificate <requireCertificate>
# use_identity_as_username true (only if useIdentityAsUsername is true)
# tls_version <value> (only if tlsVersion is non-empty)
HA cluster: install and upgrade
- Create both Secrets (server + client CA).
- Set
mosquitto.mtls.enabled=true,mosquitto.mtls.createSecret=falseif the server Secret is external. - Set
haproxy.proxyProtocol.enabled=false. - Install or upgrade, then restart HAProxy so it picks up the ConfigMap.
Kubernetes
helm upgrade --install mosquitto-platform ./mosquitto-3.2-platform-3.2-kubernetes-cluster \
-n "$NAMESPACE" \
-f values.yaml \
--set mosquitto.mtls.enabled=true \
--set mosquitto.mtls.createSecret=false \
--set haproxy.proxyProtocol.enabled=false
kubectl rollout restart deployment haproxy -n "$NAMESPACE"
OpenShift
helm upgrade --install mosquitto-platform ./mosquitto-3.2-platform-3.2-openshift-cluster \
-n "$NAMESPACE" \
-f values.yaml \
--set mosquitto.mtls.enabled=true \
--set mosquitto.mtls.createSecret=false \
--set haproxy.proxyProtocol.enabled=false
oc rollout restart deployment haproxy -n "$NAMESPACE"
Single node: install and upgrade
No HAProxy. Enable mTLS and external server Secret the same way; omit the deployment restart.
Kubernetes
helm upgrade --install mosquitto-platform ./mosquitto-3.2-platform-3.2-kubernetes-sn \
-n "$NAMESPACE" \
-f values.yaml \
--set mosquitto.mtls.enabled=true \
--set mosquitto.mtls.createSecret=false
OpenShift
helm upgrade --install mosquitto-platform ./mosquitto-3.2-platform-3.2-openshift-sn \
-n "$NAMESPACE" \
-f values.yaml \
--set mosquitto.mtls.enabled=true \
--set mosquitto.mtls.createSecret=false
Verification
# Server material
kubectl exec mosquitto-0 -n "$NAMESPACE" -- ls -la /mosquitto/config/certs/
# Client CA capath (after init)
kubectl exec mosquitto-0 -n "$NAMESPACE" -- ls -la /mosquitto/config/capath_mqtts/
# Listener and TLS directives
kubectl exec mosquitto-0 -n "$NAMESPACE" -- grep -A 15 "listener.*8883" /mosquitto/config/mosquitto.conf
Connect with an MQTT client using TLS to port 8883, server CA trust for server.pem, and a client certificate + key issued under your client CA. Example with mosquitto_sub (adjust paths and host):
mosquitto_sub -h <broker-or-haproxy-host> -p 8883 \
--cafile ca.crt \
--cert client.crt --key client.key \
-u '<user-if-needed>' -P '<pass-if-needed>' -t '#'
Rotating certificates
Server Secret
kubectl create secret generic mosquitto-mtls-server-tls \
-n "$NAMESPACE" \
--from-file=server.pem=./server.pem \
--from-file=server.key=./server.key \
--dry-run=client -o yaml | kubectl apply -f -
kubectl rollout restart statefulset mosquitto -n "$NAMESPACE"
Client CA Secret (update ca.crt and all required <hash>.0 entries, then):
kubectl create secret generic mosquitto-client-ca \
-n "$NAMESPACE" \
--from-file=ca.crt=./ca.crt \
--from-file=${HASH}.0=./${HASH}.0 \
--dry-run=client -o yaml | kubectl apply -f -
kubectl rollout restart statefulset mosquitto -n "$NAMESPACE"
For HA, restart HAProxy after broker or HAProxy ConfigMap–affecting changes:
kubectl rollout restart deployment haproxy -n "$NAMESPACE"
Troubleshooting
| Issue | What to check |
|---|---|
| Helm fails on proxy protocol | Set haproxy.proxyProtocol.enabled=false when mTLS is enabled. |
| Helm / install says client CA Secret missing | Create mosquitto.mtls.clientCaSecretName before install when validateSecrets is true. |
| Clients fail handshake / “not authorized” | Client cert signed by a CA present under capath; hash files present; require_certificate expectations; username/password vs use_identity_as_username. |
| Mosquitto rejects CA layout | Regenerate <hash>.0 with -subject_hash_old if you are on OpenSSL 3 and Mosquitto expects legacy hashes. |
| Duplicate listener / broken config | Ensure mosquitto.serverOnlyTls.enabled is false when using mTLS. |
Further reading
- Cedalo mTLS documentation: https://docs.cedalo.com/mosquitto/security/certificates/mtls
- HAProxy TLS and server-only TLS on Mosquitto:
serverOnlyTLS.mdin this repository.