EMQX 6.3 LTS vs 5.8 LTS: Breaking Changes and Migration

EMQX 6.3 LTS vs 5.8 LTS: Breaking Changes and Migration

EMQX 6.3 LTS vs 5.8 LTS: Breaking Changes and Migration

Last Updated: September 24, 2026

EMQX Enterprise 6.3.0 shipped on September 3, 2026 as the new long-term support line, and most of what makes it different from the 5.8 line will not show up as an error. A malformed MQTT 3.1.1 CONNECT is now dropped without a word. Your anonymous Prometheus scrape starts failing authentication. A Grafana panel goes flat because a metric was renamed. A WebSocket client behind a load balancer suddenly appears to come from the load balancer. Comparing EMQX 6.3 LTS vs 5.8 LTS is really a study of defaults that flipped from permissive to strict. It matters now because 5.8 support ends in August 2027, and the path from 5.8 crosses both a license boundary (5.9) and a major-version boundary (6.0). You will leave with a map of every incompatible change, the mechanism behind each one, and a staged upgrade plan built around a behavioural baseline rather than hope.

What this covers: the LTS lifecycle and the two cliffs between 5.8 and 6.3, the strict MQTT parser in detail, Feature Gates and the ESSENTIAL preset, the security and observability defaults that changed, a decision matrix, a migration runbook, and the failure modes that bite in production.

Context and Background

EMQX is an Erlang/OTP MQTT broker that scales horizontally through a core-and-replicant cluster design. In industrial deployments it usually sits between edge gateways, PLC connectors and Sparkplug B edge nodes on one side and historians, streaming platforms and cloud analytics on the other. If you are new to the wire protocol, our complete technical guide to the MQTT protocol covers the packet formats this article keeps referring to.

EMQ publishes a lifecycle policy that separates long-term support (LTS) releases from feature releases. According to the EMQX Enterprise end-of-life page, feature releases get 18 months of support, LTS releases get three years, and enterprise customers get a five-year commitment on LTS lines. Three LTS lines are live today:

  • 4.4.x — released December 2021, end of life in December 2026. This is a special five-year case.
  • 5.8.x — released August 28, 2024, end of life August 27, 2027.
  • 6.3.x — released September 3, 2026, end of life September 2, 2029.

Between 5.8 and 6.3 sit five feature releases: 5.9 (May 2, 2025), 5.10 (June 9, 2025), the 6.0 major (September 30, 2025), 6.1 (December 30, 2025) and 6.2 (March 31, 2026). The policy says the June feature release is “typically” the LTS pick. 6.3 is a September LTS, which is simply a deviation worth noting when you plan future cycles.

Two structural facts shape any 5.8 to 6.3 move. First, starting with 5.9.0, EMQX Enterprise is released under the Business Source License (BSL) 1.1, which replaced the old split between Open Source and Enterprise editions. That makes 5.8 the last LTS released before the BSL change and 6.3 the first LTS of the 6.x major. Second, EMQ’s guidance is that in-place upgrades between major versions are generally not supported, while rolling upgrades between adjacent minors are. A 5.8 to 6.3 jump crosses 6.0, so treat it as a migration project with a rollback plan, not as a routine patch.

A patch, 6.3.1, followed in mid-September (dated September 17, 2026 in EMQ release listings) and itself added three small incompatible changes. We will cover those too, because nobody should be deploying 6.3.0 at this point.

The Core Argument: 6.3 Is a Defaults Inversion, Not a Feature Drop

The short answer: EMQX 6.3 LTS differs from 5.8 LTS in three layers. It inherits every 6.0 breaking change (durable session storage, dropped OS packages, config renames). It turns permissive defaults into strict ones (MQTT parsing, Prometheus auth, ACL, proxy headers, SAML). And it adds Feature Gates, a hardened security profile and new integrations.

Most version comparisons lead with new features. That framing misleads here. The new capabilities in 6.3 are real, but they are opt-in. The breaking changes are opt-out, and several of them fail quietly. The practical consequence is that the risk in this upgrade is concentrated in behaviour you are not currently measuring.

EMQX 6.3 LTS vs 5.8 LTS lifecycle timeline showing the BSL license boundary at 5.9 and the major-version boundary at 6.0

Figure 1: The EMQX LTS timeline from 4.4 through 6.3, with the two cliffs a 5.8 cluster must cross on its way to 6.3.

The timeline shows why the jump is harder than the version numbers suggest. Between 5.8 and 6.3 there is a license cliff at 5.9, where clustering stops being free by default, and a major-version cliff at 6.0, where durable session storage and supported operating systems changed. 5.10 sits in between and removed the legacy v1 routing schema, which matters for clusters that were incrementally upgraded from early 5.x releases. Every one of these boundaries is inherited by a direct 5.8 to 6.3 move.

Cliff one: the license boundary at 5.9

The EMQX rolling upgrade guide spells out what BSL means operationally. EMQX 5.9.0 and later ships with a default EMQX Community License that enables all features but restricts deployment to a single node. If you run a clustered 5.8 deployment on the old Open Source edition and want to keep clustering, you need a commercial license. That license must be configured on each node before you start any 5.9-or-later node in the cluster. If it is missing, the node will not function correctly in the cluster.

This is the first gate in any plan, because it is a procurement problem, not a technical one. A three-node open-source 5.8 cluster cannot become a three-node 6.3 cluster by swapping packages. It needs either a commercial license in hand before the maintenance window or a redesign to a single node, which is rarely acceptable for a plant-floor broker. Clusters already on 5.8 Enterprise still need to confirm the license key location. The guide notes it goes in etc/base.hocon for recent versions (it was etc/emqx.conf before e5.8.5).

Cliff two: the major boundary at 6.0

The 6.0 release moved durable session state out of Mnesia and into EMQX’s own durable storage. Per the 6.0 incompatible changes page, clusters with durable sessions enabled must be recreated from a clean state, and pre-6.0 durable session state is lost. The rolling upgrade matrix repeats this as a footnote on every 5.x to 6.x cell. After clients reconnect, sessions on the new nodes appear clean.

For an industrial fleet, “sessions appear clean” means persistent subscriptions and queued QoS 1 and QoS 2 messages that lived in durable storage do not survive. A historian subscriber that relied on the broker to buffer data through a restart will see a gap. If you do not use durable sessions, this item does not apply, but verify that rather than assume it.

6.0 also dropped packages for Debian 10, EL/CentOS 7, Ubuntu 18.04, Ubuntu 20.04, macOS 13 and Amazon Linux 2. Many long-lived plant servers still run exactly those distributions. The config key durable_storage.messages.n_sites became durable_storage.n_sites, with new durable_storage.sessions and durable_storage.timers sections. RocketMQ’s parameters.strategy stopped accepting key templates; you now set key_dispatch explicitly and put the template in parameters.key. For Will messages on durable sessions, the authorization check now happens at disconnect time rather than after the Will-Delay-Interval.

One 6.0 change needs care depending on your path. Listener max_conn_rate and max_conn_burst are enforced per listener again rather than per acceptor. Configs written for 5.9.0, 5.9.1 or 5.10.0, which enforced per acceptor, must be rescaled. If you go straight from 5.8, your values keep their meaning. If you hop through 5.9 or 5.10 and adjust the numbers there, you must adjust them back.

Layer three: the 6.3 defaults inversion

On top of the inherited changes, 6.3 flips a long list of defaults. The full list is on the 6.3 incompatible changes page. Grouped by blast radius:

  • Device-facing: strict MQTT parsing is on; mqtt.clientid_override failures now refuse the connection (CONNACK 0x85 in MQTT 5, return code 2 in 3.1.1) instead of silently falling back.
  • Operations-facing: Prometheus endpoints require authentication; 17 VM and Mnesia metrics were renamed; the Prometheus REST API no longer returns JSON; node.max_ports and node.schedulers default to auto.
  • Security-facing: the default acl.conf no longer grants 127.0.0.1 blanket access to $SYS/# and #; WebSocket listeners stop trusting x-forwarded-for by default; Dashboard SAML verifies IdP signatures in all profiles; ACL topic templates can forbid wildcard interpolation.
  • Tenant-facing: multi_tenancy.deny_namespaces rejects global, undefined, null and none by default, and managed namespace names are validated.
  • Removed: the ExProto gateway, Google Cloud IoT Core compatibility (the GCP Device authenticator and device APIs) and the bundled Swagger UI.

Each of these is defensible on its own. Together they amount to a posture change: 5.8 assumed a trusted network and forgiving clients, and 6.3 assumes neither. That is the thesis of this post. Plan the upgrade around detecting what the new strictness rejects, not around installing new binaries.

Walk-through: What Actually Breaks and How to Detect It

This section takes the highest-risk changes one at a time, explains the mechanism, and shows how to find out whether you are affected before the upgrade rather than after it.

The strict MQTT parser

The EMQX strict mode MQTT parser is the change most likely to disconnect real devices. In 6.3, mqtt.strict_mode defaults to true. Setting it to false, globally or per zone, restores the lenient 5.8 behaviour. Strict mode enforces checks that the MQTT specification always required but that EMQX previously tolerated.

EMQX strict mode MQTT parser decision flow from incoming frame through validation checks to reason code 0x81 or silent close

Figure 2: How the 6.3 strict parser handles a frame, and why MQTT 5 and MQTT 3.1.1 clients experience a rejection very differently.

The flow shows the asymmetry that makes this change dangerous. Every failed check produces a frame_parse_error log entry at info level with a specific reason. After that, the client’s experience depends on protocol version. An MQTT 5 client receives a CONNACK or DISCONNECT with reason code 0x81 Malformed Packet, which most client libraries surface as an error. An MQTT 3.1 or 3.1.1 client has no reason code to receive, so the broker just closes the socket.

The checks, with their log reasons, are:

Check Log reason Typical real-world cause
Fixed-header reserved flags bad_frame_header Hand-rolled stacks setting DUP/QoS/RETAIN bits on non-PUBLISH packets, or sending PUBREL, SUBSCRIBE or UNSUBSCRIBE without the mandatory QoS 1 flag bits
CONNECT reserved bit reserved_connect_flag Buggy embedded clients that do not zero the reserved bit
Will flag consistency invalid_will_qos, invalid_will_retain Will QoS or Will Retain set while the Will flag is 0
Password without username (3.1.1) invalid_password_flag Devices sending a token as password only, which violates [MQTT-3.1.2-22]
UTF-8 validity and control characters utf8_string_invalid Topics or client IDs containing U+0000 to U+001F or U+007F to U+009F
Non-zero packet identifiers bad_packet_id QoS 1 or 2 packets sent with packet ID 0

Two rows deserve industrial context. The password-without-username rule is specific to MQTT 3.1.1, which says that if the username flag is 0, the password flag must be 0. MQTT 5 relaxed this, and some gateway firmware sends a JWT or shared secret in the password field alone even on a 3.1.1 connection. Those devices worked on 5.8. On 6.3 they are closed silently at CONNECT.

The control-character row catches a pattern common with PLC-derived identifiers. PLC STRING types are fixed-length buffers, and a connector that builds a client ID or topic segment from a machine serial number can leak NUL padding bytes (U+0000) into the string. That was tolerated before. It is now a parse failure.

A 3.1.1 device that fails mid-session, for example on a PUBLISH with a bad topic, has its connection closed without a DISCONNECT packet. Under standard MQTT semantics that is an abnormal disconnect, so a registered Will message fires. For a Sparkplug B edge node, the Will is its NDEATH. The node reconnects, publishes NBIRTH, sends the same bad packet and dies again. If flapping detection is enabled, the broker may then ban the client ID, username or source IP for a while. You end up with a death-birth loop that looks like a network problem. Our Sparkplug B reference architecture explains why host applications react strongly to NDEATH storms.

Detection before cutover. Stand up a single 6.3 test node with strict mode on and the log level at info or lower, since many production brokers run at warning and would hide these entries. Point a representative sample of each device firmware family at it. Every distinct firmware version should connect, subscribe, publish at each QoS it uses and disconnect cleanly. Then search the logs for frame_parse_error, grouped by reason and client ID prefix. Each distinct reason is either a firmware ticket or a documented reason to keep strict_mode = false for a zone.

## emqx.conf — restore lenient parsing globally while firmware is fixed
mqtt {
  strict_mode = false
}

A better pattern is to keep strict mode on globally and relax it only for a zone that serves the legacy listener. That keeps the rest of the fleet honest while you remediate. Check the zone syntax in the configuration manual for your exact build before relying on it.

Prometheus: auth, renames and format

Three changes land on the monitoring stack at once. First, /api/v5/prometheus/* endpoints require authentication by default. A scraper that worked anonymously against 5.8 receives authentication errors, and your “EMQX down” alert may fire, or worse, a stale-series alert may never fire because the target just disappears. The release notes recommend an API key with the monitoring scope and Bearer authentication. prometheus.enable_basic_auth = false restores anonymous scraping if you need a bridge period.

# prometheus.yml — scrape EMQX 6.3 with an API key (monitoring scope)
scrape_configs:
  - job_name: emqx
    metrics_path: /api/v5/prometheus/stats
    authorization:
      type: Bearer
      credentials_file: /etc/prometheus/emqx_monitoring_token
    static_configs:
      - targets: ["emqx-0:18083", "emqx-1:18083", "emqx-2:18083"]

Confirm the exact token format against the EMQX Prometheus integration docs for your build. The shape above is standard Prometheus configuration; what EMQX expects inside the token is the part to verify.

Second, 17 Erlang VM and Mnesia metrics were renamed to promtool-compliant names. Examples from the release notes: erlang_vm_memory_bytes_total became erlang_vm_memory_bytes, erlang_vm_process_count became erlang_vm_processes, erlang_mnesia_committed_transactions gained a _total suffix, and erlang_vm_statistics_runtime_milliseconds became erlang_vm_statistics_runtime_seconds_total. The wallclock metric follows the same pattern (..._wallclock_time_milliseconds to ..._wallclock_time_seconds_total), and the port, process and atom counts became erlang_vm_ports, erlang_vm_processes and erlang_vm_atoms. Note the unit change in the two time metrics. An alert threshold written in milliseconds will be off by a factor of 1,000 if you only rename the series.

Third, the Prometheus REST API dropped JSON output. A request with Accept: application/json now receives a 400. Custom scripts that polled the endpoint for JSON will break.

Detection. Export every PromQL expression from your dashboards and alert rules, and grep them for erlang_vm_ and erlang_mnesia_. Each hit needs a rename and possibly a unit fix. During the canary phase, run old and new queries side by side so you can see a gap before the last 5.8 node leaves.

node.max_ports and connection headroom

In 6.3, node.max_ports defaults to auto. The rule is 65,536 ports per core up to 8 cores, and 1,048,576 above 8 cores. Client connections, backend sockets and open files have historically all consumed Erlang ports. The new socket-based TCP backend may change that accounting for plain TCP listeners, but EMQ’s own guidance still ties max_ports to connection count, so follow it. A host with 8 or fewer cores therefore boots with a port table sized by its core count.

A worked estimate shows where this bites. Take a 4-vCPU replicant node that carries 280,000 device connections on 5.8, because MQTT connections are mostly idle and cheap. Under the auto rule, 4 × 65,536 = 262,144 ports. That is below 280,000 before counting any backend sockets, so the node cannot hold its former load. At 8 vCPUs the ceiling is 8 × 65,536 = 524,288. At 9 or more it jumps to 1,048,576. The numbers here are illustrative arithmetic, not a measured benchmark, but the rule itself comes from the release notes: if you need more than cores × 65,536 connections, set node.max_ports explicitly before upgrading.

The companion change, node.schedulers = auto, is good news for Kubernetes users. It caps Erlang scheduler threads using sched_getaffinity, which fixes oversubscription when a pod runs under a cpuset or CPU limit. A hidden node.process_limit setting is reinstated as an override for teams that tuned process counts before. If your Kubernetes deployment follows our EMQX cluster on Kubernetes production tutorial, re-check pod CPU limits against the new port rule, because a 2-CPU limit now implies a much smaller port table than many operators expect.

## emqx.conf — pin capacity explicitly on small-core nodes
node {
  max_ports = 1048576
  schedulers = auto
}

ACLs, proxy headers and namespaces

The default acl.conf in 6.3 no longer grants 127.0.0.1 blanket access to $SYS/# and #. This only applies if you never customised the file; the old rule is left in place as a comment. The typical casualty is a sidecar or local agent on the broker host that subscribes to # or $SYS/# for health checks or local bridging. It will now be denied.

WebSocket listeners no longer trust x-forwarded-for and x-forwarded-port by default. The proxy_address_header and proxy_port_header settings are now empty. Behind an HTTP load balancer or ingress, every WebSocket client will appear to come from the load balancer’s address. That breaks IP-based ACL rules, IP-based flapping detection and audit trails. Set the headers explicitly, and only on listeners that sit behind a proxy you control.

## Behind a trusted ingress only
listeners.ws.default {
  websocket.proxy_address_header = "x-forwarded-for"
  websocket.proxy_port_header = "x-forwarded-port"
}

A new authorization.topic_template_allow setting with plus, hash and slash controls lets you forbid interpolating +, # and / into ACL topic templates. This closes a real hole. If an ACL rule allows devices/${clientid}/# and a client registers with a client ID containing # or /, the interpolated topic can grant far more than intended. Audit rules that interpolate ${clientid} or ${username} before you lock this down, because legitimate IDs with slashes will be affected. Under the default legacy security profile the rule simply stops matching; under the hardened profile the action is denied outright.

For multi-tenant clusters, multi_tenancy.deny_namespaces defaults to ["global","undefined","null","none"]. A tenant with one of those names is rejected. Rename them first or set the list to empty. Managed namespace names must now be 1 to 255 bytes of ASCII letters, digits, ., - and _.

The EMQX upgrade 5.8 to 6.3 path

EMQX upgrade 5.8 to 6.3 decision flow with gates for OS support, license, routing schema and durable sessions before a canary roll

Figure 3: The pre-flight gates for moving a 5.8 LTS cluster to 6.3 LTS, in the order they should be cleared.

The gates are ordered by lead time. OS support and licensing take weeks to resolve. The routing schema and durable session questions decide whether you can roll at all or need a full restart. Only after those are cleared does it make sense to pin configuration and start a canary.

On whether a direct rolling upgrade is possible, the answer is in the rolling upgrade guide’s “Into v6” matrix. At the time of writing, every 5.8, 5.9 and 5.10 row lists 6.0, 6.1, 6.2 and 6.3 as “supported, with limitations”. The limitation is the durable session loss described above. The 6.3 column was still marked as a planned release in that table when we read it. Treat the direct hop as documented but newly minted: rehearse it in staging with your own config rather than trusting the matrix alone.

Two footnotes can change the plan, and the first can force a full cluster restart instead of a rolling one. Since 5.10.0, only the v2 routing storage schema is supported. Clusters still on v1, which was the default before 5.4.0 and survives in clusters that were upgraded incrementally, cannot roll to 5.10 or later. Check with the command from the guide:

emqx eval 'emqx_router:get_schema_vsn()'

If it prints v1, the documented procedure is to stop all nodes, remove broker.routing.storage_schema from config, upgrade every node, then start core nodes followed by replicants. That is downtime, so schedule it. The second footnote concerns 5.8.7 and later nodes that set OpenTelemetry headers. They can only roll to 5.9.1 or 5.10.1, or you must remove the header config during the upgrade. It applies only if you route through 5.9 or 5.10.

For the roll itself, the guide describes a core-replicant pattern. Start with one core node, then upgrade a proportionate subset of replicants (about one third if you run three cores), and keep alternating. Evacuate clients from each node first with cluster rebalancing, back up config and data directories, then stop, install and start. Do not make cluster-wide config changes during the roll, because they apply to all nodes and can leave mixed-version nodes out of sync.

Decision matrix: EMQX 6.3 LTS vs 5.8 LTS by situation

Your situation Stay on 5.8 for now Move to 6.3 now Main risk if you move
Single node, open-source 5.8, small fleet Acceptable until 2027 EOL Yes, under Community License Strict parser rejections
Clustered open-source 5.8 Until license is procured Only with commercial license on every node Nodes fail to cluster
Clustered 5.8 Enterprise, no durable sessions Low urgency Yes, after canary Monitoring breakage
Durable sessions carrying buffered QoS 1 and 2 data If downstream cannot tolerate a gap With planned drain and clean rebuild Session and queue loss
Legacy MQTT 3.1.1 firmware you cannot update Strong case Only with strict_mode = false on a zone Silent disconnects
Hosts on CentOS 7, Ubuntu 20.04 or Amazon Linux 2 Until hosts are rebuilt After OS migration or containerisation No package available
Memory-constrained edge broker Works as-is Yes, with ESSENTIAL preset Losing Dashboard and metrics
Uses ExProto or GCP IoT Core compatibility Required Not until replaced Feature removed

The matrix encodes a simple rule. The reasons to wait are external dependencies: a license, an operating system, a firmware vendor or a removed feature. The reasons to move are support runway (three years versus under one) and the new capabilities below.

What 6.3 Gives You in Return

The breaking changes are the cost. This section is the return, and it matters more than it first appears for edge and industrial brokers.

Feature Gates and the ESSENTIAL preset

EMQX Feature Gates ESSENTIAL, FULL and custom compositions resolved from EMQX_FEATURES at boot with automatic dependencies

Figure 4: Feature Gates resolve once at boot from EMQX_FEATURES; core applications are always on, and custom lists pull in their dependencies.

Per the Feature Gates documentation, EMQX_FEATURES is an environment variable read only at startup. It is not stored in HOCON files, not persisted to cluster.hocon, and cannot be changed from the Dashboard, REST API or CLI. Unset or FULL preserves today’s behaviour. ESSENTIAL starts only the core applications. A custom list names optional features such as dashboard, data_integration, gateways, multi_tenancy, metrics, plugins, mqtt_extensions, file_transfer, exhook, opentelemetry and ai.

The core that stays on under ESSENTIAL is larger than “just the broker”. Authentication and authorization are always available. So are the configuration system, CLI, license validation, durable storage, audit log, node rebalance, the retainer and TLS PSK. EMQ’s release materials state that an ESSENTIAL node idles at under 100 MB of memory and skips per-connection Dashboard statistics. That is a vendor-reported figure; measure it on your own hardware.

The dependency rules have consequences. metrics automatically enables dashboard, and so does opentelemetry, so you cannot run a headless broker that still exports Prometheus metrics. data_integration, message_transformation, schema_validation and ai all pull in schema_registry. Presets cannot be mixed with names, so ESSENTIAL,metrics is invalid. An unknown name logs invalid_feature_specification with reason unknown_feature and the node exits with a non-zero status. That fail-fast behaviour is correct, but it means a typo in a Helm value produces a crash loop rather than a warning.

For industrial edge brokers, this is the most useful change in 6.3. A line-side broker on an industrial PC typically needs MQTT, authentication, authorization and retained messages, and nothing else. On 5.8 you paid memory for the rule engine, gateways and Dashboard whether you used them or not. On 6.3, ESSENTIAL gives you a smaller attack surface and footprint with one environment variable. The trade-off is observability. Without dashboard you lose GET /api/v5/features and have to read the resolved state from the feature_gates_resolved startup log line.

# Edge broker: core only
export EMQX_FEATURES=ESSENTIAL

# Plant aggregation broker: rule engine and Prometheus, no gateways or AI
export EMQX_FEATURES=data_integration,metrics

Keep the value identical on every node. The docs warn that a mixed-feature cluster exposes different REST APIs and background applications on different nodes.

The hardened security profile

6.3 adds an optional EMQX_SECURITY_PROFILE=hardened, which EMQ plans to make the default in EMQX 7.0. Admin passwords and API keys now default to PBKDF2-HMAC-SHA256 with 600,000 iterations and a random salt; existing credentials upgrade the next time they change. There is no default node.cookie, and the cookie can come from a file:// reference or a FIFO. Flapping detection can key on client ID, username or source IP, and runs before authentication queries so a flapping client does not hammer your auth backend.

Adopting the hardened profile now is cheap insurance. If 7.0 makes it the default, 6.3 is where you want to find out what it breaks, with three years of runway, rather than on the next major.

Integrations, control and performance

6.3 adds a Google Cloud Bigtable connector, GCP attached service accounts for Pub/Sub and BigQuery, AWS IAM Roles Anywhere for Kafka, Dynatrace via OpenTelemetry with OAuth2, a PostgreSQL application_name (default emqx) and time-based disk log rotation. EMQ describes the catalogue as 50+ built-in integrations. One of the intermediate feature releases, 6.2, added Agent-to-Agent (A2A) over MQTT, subscription-level message filtering, dynamic keepalive, and Azure Event Grid and QuasarDB integrations, according to EMQ’s announcement of that release.

On the control side there are context-aware authorization preconditions and templated HTTP auth URLs per tenant (for example ${client_attrs.tenant_id}). Observability gains Topic Metrics v2 with REST CRUD and wildcards, plus emqx ctl session-top for finding slow consumers. Rounding it out are SUBSCRIBE rate limiting and Session-Expiry-Interval clamping. The last two are quietly valuable. Clamping stops a misconfigured client from requesting a near-infinite session expiry that pins memory on the broker. The MQTT 5 session semantics behind this are covered in our MQTT 5 features deep dive.

For performance, the default TCP listener now uses the Erlang/OTP socket API instead of gen_tcp/inet, JSON handling is faster, and QoS 0 flow control is fairer. A bounded queue evicts the oldest QoS 0 messages so QoS 1 and 2 traffic keeps flowing under pressure. EMQ has not published numbers for the socket backend or the JSON speedup that we could find, so treat them as directional and benchmark on your own traffic.

The fair QoS 0 behaviour changes semantics in a way worth flagging for telemetry designs. If your high-rate sensor streams use QoS 0 and your alarms use QoS 1, 6.3 will now shed old sensor samples to protect the alarms under congestion. That is usually what you want. If a consumer silently depended on every QoS 0 message arriving, it never had that guarantee, and 6.3 makes the gap more visible.

Finally, an MQTT Agent plugin (emqx_agent-1.0.0) introduces Tools, Sessions and Pipelines primitives for LLM agent workflows. It is interesting for teams experimenting with agents on the plant floor, but it is a plugin, and it has no bearing on the upgrade risk.

6.3.1: three more breaking changes

The 6.3.1 patch tightened SQL template parsing for ClickHouse, TDengine, SQL Server and MySQL batch actions. SQL comments are no longer allowed, MySQL forces off ANSI_QUOTES and NO_BACKSLASH_ESCAPES, and ClickHouse ignores batch_value_separator. Namespaced admin API keys can no longer hold disallowed scopes, so existing keys must be rotated. And the emqx ctl listeners output field typo enbale was fixed to enable, which will break any script that parsed the misspelling. Review these even though they came in a patch.

Trade-offs, Gotchas, and What Goes Wrong

Turning strict mode off is a debt, not a fix. Setting mqtt.strict_mode = false everywhere gets you through the window, but it also means malformed clients stay invisible. Scope it to one zone, attach a firmware ticket to it, and log frame_parse_error at info on that zone so you can tell when the last offender is fixed.

Silent 3.1.1 closes look like network faults. Because 3.1.1 clients get no reason code, field teams will blame Wi-Fi, cellular or the firewall first. Make the frame_parse_error search part of the incident runbook before cutover, not after the first ticket.

Monitoring breaks during the one window you most need it. Prometheus auth, the renamed metrics and the millisecond-to-second change all land when the cluster is in its most fragile mixed-version state. Fix scrape auth and dashboards against the canary before the roll starts.

The port table is sized by cores. Small-core replicants that were comfortable on 5.8 may hit the auto max_ports ceiling under the same load. Set it explicitly on every node with 8 or fewer cores.

Feature Gates fail hard. An unknown feature name stops the node. In Kubernetes that is a crash loop on every pod carrying the bad value. Validate EMQX_FEATURES in CI against the documented feature list.

Removed features have no shim. ExProto and the GCP IoT Core compatibility layer are gone. Any device fleet that authenticated through the GCP Device authenticator needs a new auth path first.

Durable session loss is not optional. There is no documented way to carry pre-6.0 durable session state across. Drain queued messages, or accept the gap, and tell downstream consumers.

Proxy headers can be over-trusted. Setting proxy_address_header on a listener that is reachable directly lets any client spoof its source IP. Set it only on listeners that exclusively sit behind your own proxy.

clientid_override now refuses connections. If the override template references an attribute some clients lack, those clients are rejected with CONNACK 0x85 or return code 2 instead of keeping their own ID. Test every client class.

BSL is a real constraint. The default Community License still covers single-node deployments (check EMQ’s licensing FAQ for the exact terms). For clusters, it turns a free upgrade into a commercial decision. Budget for it explicitly.

Practical Recommendations

The safest path treats the upgrade as an observability project first and a software deployment second. Capture a behavioural baseline on 5.8: connection counts per listener, connect success rate per firmware family, message rates per QoS, ACL deny counts and the exact PromQL your dashboards use. Build a single-node 6.3.1 canary with strict mode on and logs at info. Replay your firmware matrix against it and diff every metric and log line against the baseline. Only then clear the gates in Figure 3 and roll.

For a mid-size plant, an estimate of the effort looks like this. Budget one week for inventory and licensing, one week for the canary and firmware triage, and one maintenance window per site for the roll. That is roughly three weeks of calendar time plus procurement lead time, which is an estimate from the structure of the work, not a vendor figure. With 5.8 supported until August 27, 2027, there is no reason to rush, but there is also no reason to leave it until the last quarter.

Checklist before you roll:

  • [ ] Confirm every node’s OS has a 6.x package, or containerise.
  • [ ] Procure and stage a commercial license on every node if you cluster.
  • [ ] Run emqx eval 'emqx_router:get_schema_vsn()'; plan a full restart if it returns v1.
  • [ ] Decide what happens to durable sessions and warn downstream consumers.
  • [ ] Rename durable_storage.messages.n_sites and fix RocketMQ key_dispatch.
  • [ ] Canary with mqtt.strict_mode = true, log level info; triage every frame_parse_error.
  • [ ] Switch Prometheus scraping to an API key with monitoring scope; update 17 renamed metrics and unit changes.
  • [ ] Set node.max_ports explicitly on nodes with 8 or fewer cores.
  • [ ] Set WebSocket proxy headers on listeners behind trusted proxies.
  • [ ] Review acl.conf for local $SYS/# consumers, and audit topic templates.
  • [ ] Rename tenants that collide with deny_namespaces.
  • [ ] Fix scripts parsing enbale and rotate namespaced API keys (6.3.1).
  • [ ] Choose an EMQX_FEATURES value and validate it in CI.
  • [ ] Roll core then replicants in alternating groups, with rebalancing and backups, and no config changes mid-roll.

Frequently Asked Questions

Can I upgrade directly from EMQX 5.8 to 6.3?

EMQX’s rolling upgrade matrix lists 5.8 to 6.3 as supported with limitations. The limitation is that durable session state is lost when moving from 5.x to 6.x. The 6.3 column was still flagged as planned when we read the table, so rehearse the hop in staging. Two conditions can complicate the roll. A cluster still on the v1 routing schema needs a full restart. And if you pass through 5.9 or 5.10, OpenTelemetry header config on 5.8.7 or later restricts the target to 5.9.1 or 5.10.1. A commercial license is also needed on every node to keep clustering.

What is EMQX strict mode and should I turn it off?

Strict mode is the 6.3 default MQTT parser behaviour. It rejects frames that violate the specification: bad reserved flags, inconsistent Will flags, a password without a username on MQTT 3.1.1, invalid UTF-8 or control characters, and zero packet IDs. MQTT 5 clients get reason code 0x81, while 3.1.1 clients are disconnected silently. Keep it on globally. If legacy firmware fails, set mqtt.strict_mode = false only for the zone serving those devices, and track the firmware fixes so you can remove the exception.

What does EMQX_FEATURES=ESSENTIAL disable?

ESSENTIAL disables every optional feature: the Dashboard and REST API, data integration and the rule engine, gateways, multi-tenancy, Prometheus metrics, MQTT extensions, file transfer, plugins, ExHook, OpenTelemetry and AI features. Core applications stay on, including the MQTT broker, authentication, authorization, the retainer, durable storage, audit log, CLI and license validation. EMQ reports idle memory under 100 MB in this mode. Because the Dashboard is off, you inspect the resolved feature set through the feature_gates_resolved startup log line.

Why did my Prometheus scrape of EMQX break after upgrading?

Three reasons, often at once. In 6.3 the /api/v5/prometheus/* endpoints require authentication by default; EMQ recommends an API key with the monitoring scope using Bearer auth, and prometheus.enable_basic_auth = false restores anonymous access. Seventeen Erlang VM and Mnesia metrics were renamed to promtool-compliant names, so queries return nothing. Two time metrics, runtime and wallclock, also changed from milliseconds to seconds. Finally, JSON output was removed, so requests with Accept: application/json return HTTP 400.

Is EMQX 6.3 still open source?

No, not in the old sense. From 5.9.0 onward EMQX is released under the Business Source License 1.1, replacing the former split between an Open Source and an Enterprise edition. Every release ships with a default Community License that enables all features but limits you to a single node. Clustering requires a commercial license configured on every node. That makes 5.8 the last LTS released before the change. Read EMQ’s licensing FAQ for the exact terms, since this article is not legal advice.

How long is EMQX 5.8 supported compared with 6.3?

Per EMQ’s lifecycle page, 5.8 LTS was released on August 28, 2024 and reaches end of life on August 27, 2027. 6.3 LTS was released on September 3, 2026 and is supported until September 2, 2029. LTS releases receive three years of support, and enterprise customers get a five-year commitment on LTS lines. Feature releases like 6.1 and 6.2 get 18 months. The 4.4 line is a special case that ends in December 2026, so anyone still on 4.4 has the most urgent migration.

Further Reading

By Riju — about

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *