MQTT Security

Self-Signed Certificate MQTT Setup: When and How to Use One

A hands-on tutorial for building a private certificate authority with OpenSSL, securing a lab Mosquitto broker on port 8883 and connecting clients, plus the operational costs that make public certificates easier in production.

Self-signed certificate MQTT illustration with a private certificate authority signing a broker certificate for a lab network
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

A self-signed certificate for MQTT lets you encrypt a lab or offline broker without a public certificate authority. Done naively, it either fails verification on every client or tempts people to disable verification altogether. This tutorial shows the correct pattern, a small private CA created with OpenSSL, then configures Mosquitto on port 8883, connects Python and command-line clients, and explains when a publicly trusted certificate saves you the trouble.

Self-Signed Certificate vs Private CA

The phrase "self-signed certificate" is used for two different setups:

  • A single self-signed server certificate: the broker's certificate signs itself. Clients must trust that exact certificate. When it expires or changes, every client must be updated.
  • A private CA: you create your own root CA certificate (self-signed), and use it to sign the broker certificate. Clients trust the CA, so you can reissue the broker certificate freely as long as the CA stays the same.

The private CA approach is what you want almost every time. It costs two extra OpenSSL commands and saves you from redistributing trust every time the server certificate changes.

When a Self-Signed Certificate Makes Sense for MQTT

Scenario Self-signed / private CA Publicly trusted certificate
Laptop or lab testing Good fit Possible but unnecessary
Air-gapped factory network, no public DNS Often the only option Not possible without public DNS
Internal broker reached by IP or .local name Workable Public CAs cannot issue for these names
Internet-facing broker for devices and apps Avoid Strongly preferred
Browser dashboards over WSS Painful (browser warnings) Works out of the box
Third-party clients you do not control Painful (they must install your CA) Works out of the box

Step 1: Create the Private CA

Create a working directory and a CA key and certificate. Protect ca.key: anyone who has it can impersonate your broker.

mkdir -p ~/mqtt-ca && cd ~/mqtt-ca

openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
  -subj "/O=Example Lab/CN=Example Lab MQTT CA" -out ca.crt

Step 2: Create the Broker Certificate With a SAN

Modern TLS clients ignore the certificate's common name for hostname checks and look only at the Subject Alternative Name (SAN). Leaving out the SAN is the most common reason a self-signed MQTT setup fails verification. Include every name and IP address clients will use.

openssl genrsa -out broker.key 2048
openssl req -new -key broker.key \
  -subj "/O=Example Lab/CN=mqtt.lab.local" -out broker.csr

cat > broker.ext <<'EOF'
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:mqtt.lab.local, DNS:localhost, IP:192.168.1.50
EOF

openssl x509 -req -in broker.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 397 -sha256 -extfile broker.ext -out broker.crt

Check the result before using it:

openssl x509 -in broker.crt -noout -subject -issuer -dates -ext subjectAltName
openssl verify -CAfile ca.crt broker.crt

The second command should print broker.crt: OK. Replace the names and the IP address with your own.

Step 3: Configure Mosquitto for TLS

Copy the files to the broker and create a listener configuration:

sudo mkdir -p /etc/mosquitto/certs
sudo cp ca.crt broker.crt broker.key /etc/mosquitto/certs/
sudo chown mosquitto: /etc/mosquitto/certs/broker.key
sudo chmod 600 /etc/mosquitto/certs/broker.key
# /etc/mosquitto/conf.d/tls.conf
listener 8883
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/broker.crt
keyfile /etc/mosquitto/certs/broker.key

# Require MQTT credentials as well
allow_anonymous false
password_file /etc/mosquitto/passwd

Create a user with sudo mosquitto_passwd -c /etc/mosquitto/passwd labuser, then restart with sudo systemctl restart mosquitto and check journalctl -u mosquitto for errors such as unreadable key files.

Step 4: Connect Clients

Every client needs ca.crt, never the CA key. With the mosquitto tools:

mosquitto_sub -h mqtt.lab.local -p 8883 --cafile ca.crt \
  -u labuser -P 'your-password' -i lab-sub-1 -t "lab/#" -v

mosquitto_pub -h mqtt.lab.local -p 8883 --cafile ca.crt \
  -u labuser -P 'your-password' -i lab-pub-1 -t "lab/test" -m "hello private CA"

In Python with paho-mqtt 2.x:

import paho.mqtt.client as mqtt

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="lab-python-1")
client.tls_set(ca_certs="ca.crt")          # trust only the private CA
client.username_pw_set("labuser", "your-password")
client.connect("mqtt.lab.local", 8883)
client.loop_start()
client.publish("lab/test", "verified against private CA", qos=1).wait_for_publish()
client.loop_stop()
client.disconnect()

In Node.js, pass ca: fs.readFileSync("ca.crt") in the MQTT.js options. On an ESP32, embed the contents of ca.crt and call setCACert(). Notice that verification stays fully enabled in every example: the client simply trusts your CA instead of a public one. If you hit verification errors, our guide to SSL CERTIFICATE_VERIFY_FAILED in MQTT lists the usual causes.

Trusting the Private CA System-Wide

Passing --cafile to every tool gets tedious on lab machines. On a Linux workstation you can add the CA to the operating system store so that tools which use it, such as mosquitto_sub --capath /etc/ssl/certs, curl and most language runtimes, trust the lab broker automatically:

# Debian / Ubuntu / Raspberry Pi OS
sudo cp ca.crt /usr/local/share/ca-certificates/example-lab-mqtt-ca.crt
sudo update-ca-certificates

On Windows, import ca.crt into the "Trusted Root Certification Authorities" store with certutil -addstore -f Root ca.crt from an elevated prompt; on macOS, add it to the System keychain and mark it as trusted. Be deliberate about this. Anything signed by your lab CA becomes trusted by that machine for every purpose, not just MQTT, so protect the CA key accordingly and remove the CA from machines that leave the lab. Note that certifi-based Python clients and Node.js ignore the operating system store unless configured otherwise, so they still need the CA file.

Pitfalls to Avoid

  1. Missing SAN: hostname verification fails even though the chain is valid.
  2. Distributing the wrong file: clients need ca.crt, not broker.crt and never ca.key.
  3. Disabling verification: --insecure, tls_insecure_set(True) or rejectUnauthorized: false make the certificate pointless.
  4. Forgetting expiry: put the broker certificate's expiry date in a calendar and automate renewal if you can. An expired certificate takes every client offline at once.
  5. Over-long validity on the server certificate: long-lived certificates are convenient but increase the impact of a leaked key. Keep the CA long-lived and the broker certificate shorter.
  6. CA key on the broker: store the CA key offline. The broker only needs its own key and certificate.
  7. Clock problems: devices without accurate time reject valid certificates.

The same private CA can also sign client certificates if you want mutual TLS; see MQTT mTLS with X.509 client certificates.

Renewing the Broker Certificate

Because the private CA stays the same, renewal only touches the broker. Generate a new key (or reuse the old one if your policy allows), create a new CSR, sign it with the same CA and broker.ext file, and replace the files on the broker:

openssl genrsa -out broker-new.key 2048
openssl req -new -key broker-new.key -subj "/O=Example Lab/CN=mqtt.lab.local" -out broker-new.csr
openssl x509 -req -in broker-new.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 397 -sha256 -extfile broker.ext -out broker-new.crt

sudo cp broker-new.crt /etc/mosquitto/certs/broker.crt
sudo cp broker-new.key /etc/mosquitto/certs/broker.key
sudo systemctl restart mosquitto

Clients do not change, because they trust ca.crt, not the broker certificate. This is the payoff of the private CA pattern compared with a lone self-signed certificate, where every client would need the new file. A simple cron job that warns you a month before expiry is worth adding:

openssl x509 -checkend 2592000 -noout -in /etc/mosquitto/certs/broker.crt \
  || echo "MQTT broker certificate expires within 30 days"

The Operational Cost Nobody Mentions

A private CA is easy on day one. The cost appears later: distributing ca.crt to every new client, explaining browser warnings when someone opens a WSS dashboard, renewing the broker certificate before it expires, replacing the CA itself after a decade or a key compromise, and documenting all of it for the next engineer. For an internet-facing system, that effort buys nothing that a publicly trusted certificate does not already provide.

That is the main reason to use a managed MQTT broker for production. ConnectMQ presents a Let's Encrypt certificate that operating systems, browsers, Python and Node.js already trust, and renews it automatically. Clients connect to port 8883 or secure WebSocket on 8084 with no CA files to distribute, apart from embedding a standard public root on microcontrollers. You can confirm this in seconds on the free public MQTT broker:

mosquitto_sub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
  -i pubca-test-3e8b -t "connectmq/demo/3e8b/#" -v

For a comparison of hosting models, read our cloud MQTT broker guide, and for the full list of hardening steps see MQTT TLS configuration.

Get Started with ConnectMQ

Skip the private CA for your production broker. Sign up for ConnectMQ at app.connectmq.com, choose the free Serverless plan with no credit card, create a broker and connect on port 8883 with the generated username, password and client ID. ConnectMQ handles certificates and renewals; keep your self-signed setup for the lab where it belongs.

Frequently Asked Questions

Can I use a self-signed certificate in production MQTT?

You can on closed networks where you control every client, provided you run a proper private CA and manage renewals. For internet-facing brokers, a publicly trusted certificate is simpler and avoids distributing trust to every client.

Why does my client reject the certificate even with the CA file?

The most common reason is a missing or wrong Subject Alternative Name. Check it with openssl x509 -noout -ext subjectAltName -in broker.crt and make sure it lists the hostname clients use.

Can I get a Let's Encrypt certificate for an internal broker?

Only for a public domain name you control, using DNS validation. Public CAs do not issue certificates for private IP addresses or .local names.

Do I need to restart Mosquitto after renewing the certificate?

Mosquitto loads certificates at startup, so plan a restart or reload after renewal and test with openssl s_client afterwards.

Related articles

How to Rotate MQTT Credentials Without Downtime

A step-by-step dual-credential strategy for rotating MQTT usernames and passwords across a fleet: stage the new credential, switch with fallback, verify and only then revoke the old one.

7 min read

Storing MQTT Credentials Securely on Embedded Devices

A practical comparison of ways to protect MQTT usernames and passwords on ESP32 and Linux devices, from encrypted NVS and flash encryption to secure elements and TPM-backed systemd credentials.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.