The quickest way to use MQTT in C++ is the Eclipse Paho C++ client (paho.mqtt.cpp): create an mqtt::async_client, connect with a connect_options_builder, then publish() and subscribe() — each call returns a token you can wait() on. If your application already lives on Boost.Asio, async_mqtt is a header-only alternative that fits that model natively.
This tutorial builds a publisher and a subscriber with Paho C++, adds TLS, reconnects and MQTT 5, and finishes with how to test everything against a real broker.
Choosing a library
| Library | Model | Protocol | Good fit |
|---|---|---|---|
Paho C++ (paho.mqtt.cpp) | Thin C++ wrapper over the Paho C async library; tokens, callbacks or a consumer queue | MQTT 3.1, 3.1.1, 5; TCP, TLS, WebSocket | Most applications, Linux gateways, desktop tools |
| async_mqtt | Header-only, Boost.Asio completion tokens (callbacks, futures, C++20 coroutines) | MQTT 3.1.1 and 5; TCP, TLS, WebSocket | Asio-based servers, high connection counts, when you also want a broker/endpoint toolkit |
Paho C (paho.mqtt.c) | Plain C API | MQTT 3.1.1 and 5 | C projects, or when you cannot use C++ — see MQTT in C with Paho |
For microcontrollers neither is the usual choice; embedded C libraries such as coreMQTT or ESP-IDF's client are lighter. The rest of this guide targets Linux, macOS and Windows hosts. For a wider overview of client libraries per language, see the best MQTT libraries.
Install Paho C++
Paho C++ needs the Paho C library (built with SSL if you want TLS). Three common routes:
# Debian / Ubuntu (distribution packages can lag behind the latest release)
sudo apt install libpaho-mqtt-dev libpaho-mqttpp-dev
# vcpkg
vcpkg install paho-mqttpp3
# From source, building the bundled Paho C library with TLS
git clone https://github.com/eclipse/paho.mqtt.cpp
cd paho.mqtt.cpp && git submodule update --init
cmake -Bbuild -DPAHO_WITH_MQTT_C=ON -DPAHO_WITH_SSL=ON
sudo cmake --build build --target installA minimal CMakeLists.txt links the imported target, which pulls in the C library and OpenSSL for you:
cmake_minimum_required(VERSION 3.16)
project(mqtt_demo CXX)
set(CMAKE_CXX_STANDARD 17)
find_package(PahoMqttCpp REQUIRED)
add_executable(publish publish.cpp)
target_link_libraries(publish PRIVATE PahoMqttCpp::paho-mqttpp3)
add_executable(subscribe subscribe.cpp)
target_link_libraries(subscribe PRIVATE PahoMqttCpp::paho-mqttpp3)If you link by hand and see undefined reference to MQTTAsync_create, you are missing the C library: link paho-mqtt3as (the async, SSL-enabled variant) after paho-mqttpp3.
Publish a message
Every asynchronous call returns a token. Calling ->wait() turns it into a blocking call, which is exactly what you want in a small tool:
#include <chrono>
#include <iostream>
#include <mqtt/async_client.h>
int main() {
const std::string server = "tcp://broker.connectmq.com:1883";
mqtt::async_client client(server, "testmqtt-cpp-pub");
auto opts = mqtt::connect_options_builder()
.clean_session()
.keep_alive_interval(std::chrono::seconds(30))
.finalize();
try {
client.connect(opts)->wait();
auto msg = mqtt::make_message("testmqtt/cpp/hello", "Hello from C++", 1, false);
client.publish(msg)->wait(); // returns after PUBACK for QoS 1
std::cout << "published" << std::endl;
client.disconnect()->wait();
} catch (const mqtt::exception& e) {
std::cerr << e.what() << std::endl;
return 1;
}
}With QoS 1 the wait() on the publish token returns only after the broker acknowledged the message. With QoS 0 it returns as soon as the packet is written. See MQTT QoS levels for when each one is worth it.
Subscribe with the consumer queue
The simplest subscriber avoids callbacks entirely: start_consuming() puts incoming messages into a thread-safe queue that you read in your own loop.
#include <iostream>
#include <mqtt/async_client.h>
int main() {
mqtt::async_client client("tcp://broker.connectmq.com:1883", "testmqtt-cpp-sub");
auto opts = mqtt::connect_options_builder()
.clean_session()
.finalize();
try {
client.start_consuming(); // before connect, so nothing is missed
client.connect(opts)->wait();
client.subscribe("testmqtt/cpp/#", 1)->wait();
std::cout << "waiting for messages..." << std::endl;
while (true) {
auto msg = client.consume_message(); // blocks
if (!msg) break; // nullptr: connection lost
std::cout << msg->get_topic() << " -> " << msg->to_string() << std::endl;
}
} catch (const mqtt::exception& e) {
std::cerr << e.what() << std::endl;
return 1;
}
}Run the subscriber, then the publisher in a second terminal. You can also publish to testmqtt/cpp/hello from the online MQTT client and watch the line appear in your terminal.
Callbacks and automatic reconnect
Long-running services usually prefer callbacks and must survive network drops. Paho C++ has lambda setters for each event, and automatic_reconnect() handles the back-off:
#include <chrono>
#include <iostream>
#include <thread>
#include <mqtt/async_client.h>
int main() {
mqtt::async_client client("tcp://broker.connectmq.com:1883", "testmqtt-cpp-service");
client.set_connected_handler([&client](const std::string&) {
std::cout << "connected, subscribing" << std::endl;
client.subscribe("testmqtt/cpp/commands/#", 1); // no wait() in a callback!
});
client.set_connection_lost_handler([](const std::string& cause) {
std::cerr << "connection lost: " << cause << std::endl;
});
client.set_message_callback([](mqtt::const_message_ptr msg) {
std::cout << msg->get_topic() << " " << msg->get_payload_str() << std::endl;
});
auto opts = mqtt::connect_options_builder()
.clean_session(false)
.keep_alive_interval(std::chrono::seconds(30))
.automatic_reconnect(std::chrono::seconds(1), std::chrono::seconds(30))
.finalize();
client.connect(opts)->wait();
while (true) std::this_thread::sleep_for(std::chrono::seconds(60));
}Three rules keep callback code healthy:
- Never block in a callback. Calling
->wait()insideconnectedormessage_arrivedwaits on the very thread that should complete the token. Fire the call and move on, or push work to your own queue. - Resubscribe on connect. The connected handler runs after every successful (re)connect, so subscriptions come back even if the broker lost the session.
- Use a stable, unique client ID for
clean_session(false). Two processes with the same ID kick each other off in a loop. Generate one with the client ID generator and read reconnect best practices for back-off guidance.
Persistent sessions need a broker that stores them: the shared broker.connectmq.com endpoint keeps no offline sessions, so test clean_session(false) against your own private broker. On the shared endpoint, the resubscribe in the connected handler is what keeps the service working.
TLS and authentication
For anything beyond a quick test, connect over TLS on port 8883 with credentials. Switch the URI scheme to ssl:// and add ssl_options:
#include <cstdlib>
#include <mqtt/async_client.h>
static std::string env(const char* name) {
const char* v = std::getenv(name);
return v ? v : "";
}
int main() {
mqtt::async_client client("ssl://broker.connectmq.com:8883", "testmqtt-cpp-tls");
auto ssl = mqtt::ssl_options_builder()
.trust_store("/etc/ssl/certs/ca-certificates.crt") // system CA bundle
.enable_server_cert_auth(true)
.finalize();
auto opts = mqtt::connect_options_builder()
.user_name(env("MQTT_USER"))
.password(env("MQTT_PASS"))
.ssl(std::move(ssl))
.finalize();
client.connect(opts)->wait();
client.publish("testmqtt/cpp/secure", "over TLS", 1, false)->wait();
client.disconnect()->wait();
}The CA bundle path differs per OS (/etc/pki/tls/certs/ca-bundle.crt on RHEL-family systems; on macOS export one or point to Homebrew's OpenSSL bundle). If the connection fails before CONNACK, verify the certificate chain from the same machine with the steps in testing an MQTT TLS connection.
MQTT 5 with Paho C++
MQTT 5 is opt-in. Create the client with MQTT 5 options and use the v5 builder, which also accepts properties:
mqtt::async_client client("tcp://broker.connectmq.com:1883", "testmqtt-cpp-v5",
mqtt::create_options(MQTTVERSION_5));
auto opts = mqtt::connect_options_builder::v5()
.clean_start(false)
.properties({{mqtt::property::SESSION_EXPIRY_INTERVAL, 3600}})
.finalize();
client.connect(opts)->wait();With MQTT 5 the broker returns reason codes instead of the old one-byte return codes, so a failure tells you much more (for example 0x87 Not authorized or 0x97 Quota exceeded). Look up any code in the MQTT reason codes tool. Differences between the versions are summarized in MQTT 5 vs 3.1.1.
async_mqtt: the Asio-native option
async_mqtt is a header-only C++17 library built on Boost.Asio, from the author of the older mqtt_cpp. Every operation takes an Asio completion token, so the same call can use a callback, a std::future or co_await in a C++20 coroutine. It supports MQTT 3.1.1 and 5 over TCP, TLS and WebSocket, and also provides the building blocks for a broker or server endpoint.
Choose it when:
- your program already runs an
io_context(an Asio-based server, a gateway with many sockets), - you want coroutines instead of callback chains,
- you prefer a header-only dependency over a compiled C library.
Its API has changed between major versions, so start from the examples in the repository that match the release you install rather than from older snippets. Conceptually the flow is the same as Paho: open the transport, send CONNECT and wait for CONNACK, send SUBSCRIBE, then loop receiving PUBLISH packets.
Common errors
| Message | Likely cause | Fix |
|---|---|---|
MQTT error [-1]: TCP/TLS connect failure | Wrong host or port, firewall, or TLS handshake failure | Check the URI scheme matches the port (tcp:// 1883, ssl:// 8883), then test the port from the same machine |
Connect fails immediately on an ssl:// URI | Paho C built without SSL support | Rebuild with -DPAHO_WITH_SSL=ON or use a package compiled with OpenSSL |
| Connection lost every few seconds | Another client uses the same client ID | Give each process a unique ID |
undefined reference to MQTTAsync_* | C library not linked | Link PahoMqttCpp::paho-mqttpp3 via CMake, or add paho-mqtt3as |
CONNACK refused (rc 4/5, or 0x86/0x87 in MQTT 5) | Bad credentials or not authorized | Verify username, password and ACLs |
Test it end to end
Before blaming your code, prove the broker side works with a known-good client. Start a terminal subscriber and run your publisher:
mosquitto_sub -h broker.connectmq.com -p 1883 -t 'testmqtt/cpp/#' -vThen reverse it — run your C++ subscriber and send a message from the command line:
mosquitto_pub -h broker.connectmq.com -p 1883 -t 'testmqtt/cpp/commands/led' -m 'on' -q 1To exercise reconnects, stop your network interface for longer than the keep-alive interval and confirm that the connection-lost handler fires, the client reconnects and messages flow again. If you need boilerplate in another language for the other side of the test, the MQTT code generator produces it for you.
Watch the C++ topics in the online client
Paho C++ gives you a dependable MQTT client with very little code: a builder for options, tokens for control flow, and callbacks or a consumer queue for incoming messages. Keep callbacks non-blocking, enable automatic reconnect with stable client IDs, use TLS from the start, and reach for async_mqtt when your program is already an Asio application.
Frequently asked questions
Which MQTT library should I use in C++?
For most applications, Eclipse Paho C++ (paho.mqtt.cpp): it is mature, supports MQTT 3.1.1 and 5, TLS and WebSockets, and wraps the widely used Paho C library. async_mqtt is a good choice if your code is already built on Boost.Asio and you want a header-only library with coroutine support.
Why does my Paho C++ program freeze inside a callback?
You probably called ->wait() on a token inside a callback such as message_arrived or connected. Callbacks run on the library's thread, so blocking there waits on itself. Start the operation without waiting, or hand the work to another thread or the consumer queue.
Does Paho C++ reconnect automatically?
Yes, if you enable automatic_reconnect() in connect_options_builder. Combine it with a persistent session (clean_session(false) and a fixed client ID) or resubscribe in the connected handler, otherwise you reconnect without any subscriptions.


