Library / SDK
emqx/CocoaMQTT avatar
emqx/CocoaMQTT

CocoaMQTT 2.x LTS: an MQTT 5 client for Apple platforms, and the TLS options that actually bite

MQTT 5.0 client library for iOS and macOS written in Swift

1,753 stars474 forksSwiftNOASSERTION

At a glance

What is it?
CocoaMQTT is EMQX's Swift client for MQTT 3.1.1 and 5.0 on iOS, macOS, tvOS and visionOS, distributed through Swift Package Manager and CocoaPods. This branch is the 2.x long term support line, and its most detailed documentation is about certificate handling rather than topics and subscriptions.
Who is it for?
CocoaMQTT fits an Apple platform application that needs MQTT 5 connect properties such as `topicAliasMaximum` and `sessionExpiryInterval`, or that needs a private CA and mutual TLS without writing a transport layer. It does not fit a team that wants the newest development line, since this branch is the 2.x LTS with 3.x continuing on master, nor one that needs encrypted or EC private keys, which the PEM importer does not accept.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 45 days ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Two release lines, and you are reading the LTS one

The default branch of this repository is `release/2.x`, and the README opens by saying so. It describes the branch as the latest stable 2.x release line, states that it preserves the established CocoaAsyncSocket and Starscream transport architecture, and says it receives compatibility, correctness and security fixes. CocoaMQTT 3 development continues on `master`, and the README points to `MAINTENANCE.md` and `CHANGELOG.md` for the policy.

That framing matters more than a version number. A long term support line is a promise about what will not change structurally, and the pinned transport architecture is named explicitly: CocoaAsyncSocket for TCP and Starscream for WebSocket on older systems. If you are starting a new integration, the question is whether you want that stability or the work happening on master, and the two have different risk profiles.

The tags tell the rest. 2.4.1 was published on 2026-09-08, 2.4.0 on 2026-07-30 and 2.3.0 on 2026-06-30, and the last push was on 2026-09-08. The library is written in Swift 5, the package manifest requires Swift tools 5.7, and the README notes that both Swift Package Manager products can be used by Swift 6 applications and are covered by a Swift 6 compatibility check in CI.

Two installation routes with one real difference: visionOS

Swift Package Manager is the primary route. In Xcode you go to `File` > `Swift Packages` > `Add Package Dependency`, enter `https://github.com/emqx/CocoaMQTT.git`, choose a version or range, and add the package to your target.

SPM supports iOS, macOS, tvOS and visionOS, and both the `CocoaMQTT` and `CocoaMQTTWebSocket` products are compile-verified against the visionOS SDK in CI. Usage starts with:

swift
import CocoaMQTT

CocoaPods works too, with a Podfile:

ruby
use_frameworks!

target 'Example' do
    pod 'CocoaMQTT'
end

followed by `pod install`.

The limitation is stated in a blockquote rather than buried: CocoaPods installation is not currently supported for visionOS, because the transitive CocoaPods dependencies do not declare visionOS compatibility, so Swift Package Manager must be used for visionOS applications. CocoaPods also uses Swift 5 language mode.

Deployment targets are listed per platform: iOS 12.0 or above, macOS 10.13 or above, tvOS 12.0 or above with SPM or CocoaPods WebSockets but 10.0 or above with CocoaPods Core, and visionOS 1.0 or above through SPM only. That tvOS split is the kind of detail that decides whether your Podfile resolves.

Two client classes, and the MQTT 5 properties only one of them has

The library exposes `CocoaMQTT` for MQTT 3.1.1 and `CocoaMQTT5` for MQTT 5.0. Both take a client identifier, a host and a port, and both expose `username`, `password`, `willMessage`, `keepAlive`, `delegate` and `connect()`.

The MQTT 5 client adds the connect properties that version 5 introduced:

swift
let clientID = "CocoaMQTT-" + String(ProcessInfo().processIdentifier)
let mqtt5 = CocoaMQTT5(clientID: clientID, host: "broker.emqx.io", port: 1883)

let connectProperties = MqttConnectProperties()
connectProperties.topicAliasMaximum = 0
connectProperties.sessionExpiryInterval = 0
connectProperties.receiveMaximum = 100
connectProperties.maximumPacketSize = 500
mqtt5.connectProperties = connectProperties

mqtt5.username = "test"
mqtt5.password = "public"
mqtt5.willMessage = CocoaMQTTMessage(topic: "/will", string: "dieout")
mqtt5.keepAlive = 60
mqtt5.delegate = self
mqtt5.connect()

Each of those four properties is a negotiation with the broker rather than a local setting. `receiveMaximum` of 100 tells the broker how many QoS 1 and 2 messages it may have in flight to you, and `maximumPacketSize` of 500 is a hard ceiling on what the broker may send. A client that sets `maximumPacketSize` too low relative to the payloads a broker produces will see large messages rejected rather than delivered, which is a confusing symptom to debug if you assumed the property was advisory.

The client identifier in the example appends `ProcessInfo().processIdentifier`, which is the pattern for avoiding a collision between two instances of the same app connecting to the same broker.

Receiving is either through the `CocoaMQTTDelegate` or through closures, and the closure form is the shorter path:

swift
mqtt.didReceiveMessage = { mqtt, message, id in
    print("Message received in topic \(message.topic) with payload \(message.string!)")
}

TLS: enableSSL is the easy case, tlsServerName is the trap

For a broker whose certificate chains to the Apple system trust store, TLS is one property:

swift
mqtt.enableSSL = true

CocoaMQTT verifies the certificate against the broker host by default. The exception worth knowing is connecting to an IP address when the certificate is issued to a DNS name. Name-based verification then has nothing to match, and the README says to set the name explicitly:

swift
mqtt.tlsServerName = "broker.example.com"

This is the most common self-hosted broker failure after the network itself is fine: the TCP connection opens, TLS negotiates, and verification fails because the address you dialled is not the identity in the certificate.

For a private or self-signed CA, load a DER or PEM encoded certificate from the bundle and decide whether the system trust store is also acceptable:

swift
let data = try Data(contentsOf: Bundle.main.url(forResource: "broker-ca", withExtension: "crt")!)
guard let certificate = CocoaMQTTSocket.serverCertificate(from: data) else {
    fatalError("Invalid CA certificate")
}

mqtt.trustedServerCertificates = [certificate]
mqtt.usesSystemTrustStore = false // Trust only this CA.
mqtt.enableSSL = true

Setting `usesSystemTrustStore = false` is what restricts trust to your CA rather than adding to the system anchors.

manuallyEvaluateTrust, and the legacy property that does not do what its name says

For certificate pinning or an enterprise policy, the README directs you to set `manuallyEvaluateTrust = true` and implement either the trust delegate method or the `didReceiveTrust` closure.

Three warnings come with it, and they are worth reading as one block.

Never accept every certificate in production. The legacy `allowUntrustCACertificate` property only enables this manual evaluation; it does not safely trust a private CA by itself. And enabling manual evaluation without a trust callback or custom CA certificates rejects the connection.

That last sentence describes a confusing runtime failure: a client that appears correctly configured simply fails to connect, because turning on manual evaluation without supplying the evaluator removes the default verification path and adds nothing back. The property name `allowUntrustCACertificate` is a good example of why the warning exists, since it reads as permission to accept anything when it is in fact a switch to a mode you must then implement.

The README also flags the PEM/DER importer as requiring macOS 10.14, iOS 12 or tvOS 12, which is a floor slightly different from the stated macOS deployment target of 10.13. On 10.13, the private CA and mutual TLS paths are not available even though the library otherwise supports that release.

Mutual TLS over TCP and WSS, with a transport that changes underneath

Client certificates are supported, and the details are where a WSS deployment can differ from a TCP one.

For TCP you create `CocoaMQTT` or `CocoaMQTT5` normally. For WSS the README points at the mutual TLS configuration examples in `Example/Example/MutualTLSConfiguration.swift`. `CocoaMQTTWebSocket` automatically uses Apple's `URLSessionWebSocketTask` on macOS 10.15, iOS 13, tvOS 13 and visionOS 1 and later, with no transport selection required from you. On older supported OS versions it falls back to Starscream.

The fallback has a consequence that is easy to miss: on Starscream, `clientIdentity` does not participate in the TLS handshake. Mutual TLS therefore behaves differently depending on the OS version of the device, and the configuration that works on a current iPhone will not authenticate on an older one that falls back.

You apply either a PEM/DER configuration or a PKCS#12 configuration before calling `connect()`, and both work for MQTT 3.1.1 and MQTT 5 over TCP or WSS with `URLSessionWebSocketTask`, configuring the client identity and broker trust separately.

The accepted formats are narrower than the name suggests. `certificateData` takes a DER certificate, a single PEM certificate, or a PEM bundle with the leaf first followed by its intermediates. `privateKeyData` takes RSA PKCS#1 (`RSA PRIVATE KEY`) or unencrypted RSA PKCS#8 (`PRIVATE KEY`) input, and encrypted and EC PEM private keys are not currently supported. The intermediate array is flattened in the supplied order after any certificates in the leaf bundle, duplicates and a repeated leaf are ignored, and you pass the leaf issuer first and normally exclude the root. It is independent from `trustedServerCertificates`, which validates the broker. Use the `tlsServerName` parameter when the connection host differs from the DNS name in the broker certificate.

Editorial conclusion

CocoaMQTT fits an Apple platform application that needs MQTT 5 connect properties such as `topicAliasMaximum` and `sessionExpiryInterval`, or that needs a private CA and mutual TLS without writing a transport layer. It does not fit a team that wants the newest development line, since this branch is the 2.x LTS with 3.x continuing on master, nor one that needs encrypted or EC private keys, which the PEM importer does not accept. Verify first which branch your dependency resolves to, that `mqtt.enableSSL` connects to your broker under the system trust store, and that your client key is an unencrypted RSA PKCS#1 or PKCS#8 PEM before you wire the mutual TLS path.

Frequently asked questions

How do I add CocoaMQTT to an iOS project?

Through Swift Package Manager: in Xcode use File > Swift Packages > Add Package Dependency and enter https://github.com/emqx/CocoaMQTT.git, then add the package to your target and `import CocoaMQTT`. CocoaPods with `pod 'CocoaMQTT'` also works.

Can I use CocoaMQTT on visionOS?

Yes, through Swift Package Manager only. The README states CocoaPods installation is not currently supported for visionOS because the transitive CocoaPods dependencies do not declare visionOS compatibility.

Which MQTT versions does CocoaMQTT support?

Both MQTT 3.1.1 and MQTT 5.0, through two client classes: CocoaMQTT for 3.1.1 and CocoaMQTT5 for 5.0, with connect properties such as receiveMaximum and maximumPacketSize available on the latter.

How do I trust a private CA with CocoaMQTT?

Load the DER or PEM certificate with CocoaMQTTSocket.serverCertificate(from:), assign it to trustedServerCertificates, set usesSystemTrustStore to false to trust only that CA, and enable TLS with enableSSL.

What does allowUntrustCACertificate do in CocoaMQTT?

According to the README it only enables manual trust evaluation. It does not safely trust a private CA by itself, and enabling manual evaluation without a trust callback or custom CA certificates rejects the connection.

Does CocoaMQTT support mutual TLS with a PKCS#12 client identity?

Yes. A PKCS#12 configuration example is referenced for client identity, and it works for MQTT 3.1.1 and MQTT 5 over TCP or WSS. The PEM/DER alternative accepts RSA PKCS#1 or unencrypted RSA PKCS#8 keys; encrypted and EC PEM keys are not currently supported.

Official sources

  1. emqx/CocoaMQTT on GitHub
  2. Issues
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/emqx-cocoamqtt.svg)](https://hysenlabs.com/projects/emqx-cocoamqtt)

Was this useful?