BifroMQ
Overview
Apache BifroMQ (Incubating) is a distributed MQTT broker written in Java and built around native multi-tenancy. It implements MQTT 3.1, 3.1.1 and 5.0. Every connection is resolved to a tenant, and each tenant's topics, subscriptions and sessions are isolated from every other tenant on the same broker.
This SecureAnyCloud image deploys BifroMQ 4.0.0-incubating as a hardened single-node broker. By the time first boot completes, an authenticated MQTT endpoint is answering on TCP 1883, with no configuration required.
What you get:
- Apache BifroMQ 4.0.0-incubating, installed from the official Apache distribution and SHA-512 verified
- OpenJDK 21 (headless) runtime
mosquitto-clients(mosquitto_pub,mosquitto_sub) for testing on the VM- MQTT over TCP on port 1883, supporting MQTT 3.1, 3.1.1 and 5.0
- Username and password authentication enforced on every connection
- A unique MQTT password generated on each VM's first boot
- Administrative HTTP API and metrics bound to loopback and firewalled
- JVM heap sized for the VM, rather than left at a default that would exhaust it
- 24/7 SecureAnyCloud support
Why security matters for this product
Left at its own defaults, BifroMQ loads a development authentication provider that accepts every connection without checking the password, and grants every publish and subscribe. A stock deployment is an open broker.
This image replaces that provider with real credential checking. It also closes a related upstream behaviour, where a broker whose authentication service is unreachable silently grants every action instead of denying it. If you build your own BifroMQ deployment from upstream, get these two things right first.
Prerequisites
- Recommended VM size: Standard_B2s (2 vCPU / 4 GB) for evaluation and light fleets.
- OS disk: 30 GB Standard SSD or larger.
- Inbound NSG rules: TCP 22 (SSH, your admin IP only) and TCP 1883 (MQTT, your device networks only).
- Do NOT open TCP 8091 and 9090. See Step 6.
Deploy the VM
Deploy the SecureAnyCloud BifroMQ image from the Azure Portal, or from the CLI:
az group create --name bifromq-rg --location eastus
az network nsg create --resource-group bifromq-rg --name bifromq-nsg
az network nsg rule create --resource-group bifromq-rg --nsg-name bifromq-nsg \
--name allow-ssh --priority 1000 --destination-port-ranges 22 \
--source-address-prefixes <your-mgmt-cidr> --access Allow --protocol Tcp
az network nsg rule create --resource-group bifromq-rg --nsg-name bifromq-nsg \
--name allow-mqtt --priority 1010 --destination-port-ranges 1883 \
--source-address-prefixes <your-mgmt-cidr> --access Allow --protocol Tcp
az vm create --resource-group bifromq-rg --name bifromq-vm \
--size Standard_B2s --admin-username azureuser --generate-ssh-keys \
--nsg bifromq-nsg --public-ip-sku StandardScope both rules to the narrowest source range that works for you. MQTT on 1883 is authenticated, but there is no reason to expose it to the whole internet.
Connect and confirm the broker is running
sudo systemctl status bifromq.service --no-pagerThree services make up the appliance:
bifromq-firstboot.servicegenerates this VM's credential.bifromq-authd.serviceis the local authentication service.bifromq.serviceis the broker itself.
sudo systemctl is-active bifromq-firstboot.service bifromq-authd.service bifromq.service
sudo ss -tln | grep -E ':1883|:8091'Give the broker a moment on first boot. BifroMQ is a JVM application with embedded RocksDB storage. Port 1883 accepts connections within a few seconds, but the broker needs roughly one to two minutes after boot before it will serve traffic.
During that window a client is refused with Connection Refused: broker unavailable, and subscriptions can be refused for slightly longer than publishes. This is warm-up, not a fault. Retry and the client will connect.
Read this VM's MQTT credentials
There is no default password in the image. A unique password is generated on each VM's first boot and written to a root-only file:
sudo cat /root/bifromq-credentials.txtThe username is sac/admin. The part before the slash is the tenant, which matters for publishing and subscribing. See Step 5.
Publish and subscribe
This command runs a complete round trip on the VM itself. It reads the credentials, subscribes in the background, publishes one message, and prints what arrived:
U=$(sudo grep '^BIFROMQ_MQTT_USERNAME=' /root/bifromq-credentials.txt | cut -d= -f2-)
P=$(sudo grep '^BIFROMQ_MQTT_PASSWORD=' /root/bifromq-credentials.txt | cut -d= -f2-) timeout 20 mosquitto_sub -h 127.0.0.1 -p 1883 -i demo-sub -u "$U" -P "$P" \
-t 'demo/#' -q 1 -v > /tmp/mqtt-demo.txt 2>&1 & sleep 4 mosquitto_pub -h 127.0.0.1 -p 1883 -i demo-pub -u "$U" -P "$P" \
-t demo/sensors/temp -q 1 -m '22.5' sleep 3 cat /tmp/mqtt-demo.txtExpected output:
demo/sensors/temp 22.5To connect from your own machine instead, use the VM's public IP address:
mosquitto_sub -h <vm-ip> -p 1883 -u 'sac/admin' -P '<your-password>' -t 'demo/#' -q 1 -v mosquitto_pub -h <vm-ip> -p 1883 -u 'sac/admin' -P '<your-password>' -t demo/hello -q 1 -m 'hi'Two things to know before you test
Use QoS 1 where delivery matters. BifroMQ routes messages through an asynchronous subscription table. A QoS 0 publish that arrives before a subscription has fully propagated is dropped silently.
That behaviour is correct MQTT, because QoS 0 is explicitly best effort with no delivery guarantee. On a freshly created subscription, however, it is very noticeable. Every example in this guide uses -q 1.
Publisher and subscriber must share a tenant. The tenant is the part of the username before the slash. Two clients in different tenants cannot see each other's messages, and neither client gets an error. You simply get silence, which is easy to mistake for a broken broker.
With the single credential this image ships, both ends use sac/admin, so the issue does not arise. It becomes relevant as soon as you add tenants of your own.
Confirm authentication is actually enforced
Run these checks once. An MQTT broker that silently accepts everything looks identical to a working one until it matters. Both of these connection attempts must be refused:
U=$(sudo grep '^BIFROMQ_MQTT_USERNAME=' /root/bifromq-credentials.txt | cut -d= -f2-) if mosquitto_pub -h 127.0.0.1 -p 1883 -u "$U" -P 'wrong-password' \
-t demo/hello -q 1 -m nope 2>/dev/null; then echo 'PROBLEM: a wrong password was accepted' else echo 'Wrong password correctly rejected' fi if mosquitto_pub -h 127.0.0.1 -p 1883 -t demo/hello -q 1 -m nope 2>/dev/null; then echo 'PROBLEM: an anonymous connection was accepted' else echo 'Anonymous connection correctly rejected' fiExpected output:
Wrong password correctly rejected Anonymous connection correctly rejectedThe administrative API
BifroMQ has no web console or CLI. Management is through an HTTP API. That API performs no authentication of its own, and it can publish to any topic and terminate any session. For that reason, this image binds it to loopback and enforces that with a firewall rule. The plugin metrics endpoint on 9090 is treated the same way.
Check broker health locally:
curl -s http://127.0.0.1:8091/clusterTo reach the API from your own machine, tunnel over SSH rather than opening the port:
ssh -L 8091:127.0.0.1:8091 azureuser@<vm-ip>Then browse to http://127.0.0.1:8091/cluster on your own machine. Do not add an NSG rule for 8091 or 9090.
Where everything lives
These are the key components and their paths:
- Distribution: /opt/bifromq/current
- Configuration: /opt/bifromq/current/conf/standalone.yml
- JVM and runtime settings: /etc/default/bifromq
- Data (RocksDB stores): /var/lib/bifromq/
- Logs: /var/log/bifromq/
- Credential store: /etc/bifromq/mqtt-users
- Your credentials: /root/bifromq-credentials.txt
- Broker unit: /etc/systemd/system/bifromq.service
- Authentication service: /etc/systemd/system/bifromq-authd.service
Logs are verbose and split by level. Start with these commands:
sudo tail -n 20 /var/log/bifromq/info.logChanging the MQTT password
Credentials live in /etc/bifromq/mqtt-users, one record per line, as username:salt:hash:tenant:user. The password is stored as a salted SHA-256 digest, never in plain text.
To rotate the password, generate a new salt and digest and replace the record:
NEWPW='choose-a-strong-password'
SALT=$(openssl rand -hex 16)
DIGEST=$(SALT_HEX="$SALT" PW="$NEWPW" python3 -c " import binascii, hashlib, os print(hashlib.sha256(binascii.unhexlify(os.environ['SALT_HEX']) + os.environ['PW'].encode()).hexdigest()) ") printf 'sac/admin:%s:%s:sac:admin\n' "$SALT" "$DIGEST" | \
sudo tee /etc/bifromq/mqtt-users > /dev/null
sudo chown root:bifromq /etc/bifromq/mqtt-users
sudo chmod 640 /etc/bifromq/mqtt-users echo 'Password rotated'The change takes effect on the next connection. The file is re-read on every request, so no restart is needed. Update /root/bifromq-credentials.txt too if you want it to stay accurate.
To add a second tenant, append another record with a different username and tenant, for example acme/sensor:...:acme:sensor.
Scaling up
The JVM is deliberately sized for a 4 GB Standard_B2s. BifroMQ's own start script would otherwise claim about 70% of system memory as heap, plus a further 20% as direct memory. That does not fit on a small VM once RocksDB's off-heap usage is included.
If you move to a larger VM size, raise the heap to match by editing JVM_HEAP_OPTS in /etc/default/bifromq. Keep -XX:MaxDirectMemorySize set explicitly. If you remove it, direct memory silently defaults to the maximum heap size and doubles the footprint.
Leave headroom for RocksDB, which allocates outside the heap. Restart the broker with sudo systemctl restart bifromq.service.
Enabling TLS
The image ships without a certificate, so MQTT over TLS is disabled. To enable it, follow these steps:
- Place your certificate and key on the VM.
- Set
tlsListener.enable: truewith thecertFileandkeyFilepaths undermqttServiceConfig.server.tlsListenerin/opt/bifromq/current/conf/standalone.yml. - Restart the broker.
- Open TCP 1884 in the NSG.
Clients then connect with --cafile against port 1884.
Security notes
- Keep 1883 scoped in the NSG to the networks your devices actually use.
- Never open 8091 or 9090. Both are unauthenticated. Use the SSH tunnel in Step 6.
- Rotate the shipped password if the VM will be long-lived (Step 8).
- Use TLS (Step 10) for anything crossing an untrusted network. Plain MQTT sends credentials in the clear.
- Patch regularly. Unattended security upgrades are enabled. Reboot periodically to pick up kernel updates.
Licensing
Apache BifroMQ is licensed under Apache-2.0 and is free to use. BifroMQ is currently undergoing incubation at the Apache Software Foundation. Incubation status does not necessarily reflect the completeness or stability of the code, and it indicates that the project has yet to be fully endorsed by the ASF.
SecureAnyCloud provides the packaging, hardening and commercial support separately. Contact sales@secureanycloud.com.
Deploy on Azure
Launch Apache BifroMQ (Incubating) MQTT Broker on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com
Overview
Apache BifroMQ (Incubating) is a distributed MQTT broker written in Java and built around native multi-tenancy. It implements MQTT 3.1, 3.1.1 and 5.0. Every connection is resolved to a tenant, and each tenant's topics, subscriptions and sessions are isolated from every other tenant on the same broker.
This SecureAnyCloud image deploys BifroMQ 4.0.0-incubating as a hardened single-node broker. By the time first boot completes, an authenticated MQTT endpoint is answering on TCP 1883, with no configuration required.
What you get:
- Apache BifroMQ 4.0.0-incubating, installed from the official Apache distribution and SHA-512 verified
- OpenJDK 21 (headless) runtime
mosquitto-clients(mosquitto_pub,mosquitto_sub) for testing on the instance- MQTT over TCP on port 1883, supporting MQTT 3.1, 3.1.1 and 5.0
- Username and password authentication enforced on every connection
- A unique MQTT password generated on each instance's first boot
- Administrative HTTP API and metrics bound to loopback and firewalled
- JVM heap sized for the instance, rather than left at a default that would exhaust it
- 24/7 SecureAnyCloud support
Why security matters for this product
Left at its own defaults, BifroMQ loads a development authentication provider that accepts every connection without checking the password, and grants every publish and subscribe. A stock deployment is an open broker.
This image replaces that provider with real credential checking. It also closes a related upstream behaviour, where a broker whose authentication service is unreachable silently grants every action instead of denying it. If you build your own BifroMQ deployment from upstream, get these two things right first.
Connecting to your instance
Connect over SSH on port 22 as the login user for the operating system variant you launched:
- Ubuntu 24.04 LTS: SSH login user ubuntu
ssh ubuntu@<vm-ip>Prerequisites
- Recommended instance type: m5.large for production. A smaller type suffices for evaluation and light fleets.
- Root volume: 30 GB gp3 or larger.
- Inbound security group: TCP 22 (SSH, your admin IP only) and TCP 1883 (MQTT, your device networks only).
- Do NOT open TCP 8091 and 9090. See Step 6.
The recommended launch security group opens only SSH. MQTT on 1883 is authenticated, but open it only to the client networks that actually need it.
Launch the instance
Subscribe to the listing and launch the AMI from the AWS console, or from the CLI. Scope the MQTT rule to the narrowest source range that works for you:
aws ec2 create-security-group --group-name bifromq-sg \
--description "Apache BifroMQ" --vpc-id <your-vpc-id>
# SSH from your admin network only
aws ec2 authorize-security-group-ingress --group-name bifromq-sg \
--protocol tcp --port 22 --cidr <your-mgmt-cidr>
# MQTT from your device networks only
aws ec2 authorize-security-group-ingress --group-name bifromq-sg \
--protocol tcp --port 1883 --cidr <your-mgmt-cidr>
aws ec2 run-instances --image-id ami-xxxxxxxxxxxxxxxxx \
--instance-type m5.large --key-name <your-key> \
--security-groups bifromq-sgThere is no reason to expose 1883 to the whole internet. Never open 8091 or 9090 at all (Step 6).
Connect and confirm the broker is running
Three services make up the appliance:
bifromq-firstboot.servicegenerates this instance's credential.bifromq-authd.serviceis the local authentication service.bifromq.serviceis the broker itself.
sudo systemctl is-active bifromq-firstboot.service bifromq-authd.service bifromq.service
sudo ss -tln | grep -E ':1883|:8091'Give the broker a moment on first boot. BifroMQ is a JVM application with embedded RocksDB storage. Port 1883 accepts connections within a few seconds, but the broker needs roughly one to two minutes after boot before it will serve traffic.
During that window a client is refused with Connection Refused: broker unavailable, and subscriptions can be refused for slightly longer than publishes. This is warm-up, not a fault. Retry and the client will connect.
Read this instance's MQTT credentials
There is no default password in the image. A unique password is generated on each instance's first boot and written to a root-only file:
sudo cat /root/bifromq-credentials.txtThe username is sac/admin. The part before the slash is the tenant, which matters for publishing and subscribing. See the note in Step 4.
Publish and subscribe
This command runs a complete round trip on the instance itself. It reads the credentials, subscribes in the background, publishes one message, and prints what arrived. Because the broker may still be warming up, the command retries the publish for a short while:
U=$(sudo grep '^BIFROMQ_MQTT_USERNAME=' /root/bifromq-credentials.txt | cut -d= -f2-)
P=$(sudo grep '^BIFROMQ_MQTT_PASSWORD=' /root/bifromq-credentials.txt | cut -d= -f2-) timeout 90 mosquitto_sub -h 127.0.0.1 -p 1883 -i demo-sub -u "$U" -P "$P" \
-t 'demo/#' -q 1 -v > /tmp/mqtt-demo.txt 2>&1 & for i in $(seq 1 20); do mosquitto_pub -h 127.0.0.1 -p 1883 -i demo-pub -u "$U" -P "$P" \
-t demo/sensors/temp -q 1 -m '22.5' 2>/dev/null grep -q '22.5' /tmp/mqtt-demo.txt && break sleep 3 done cat /tmp/mqtt-demo.txtExpected output:
demo/sensors/temp 22.5To connect from your own machine instead, use the instance's public IP address. Open 1883 to your client network first:
mosquitto_sub -h <vm-ip> -p 1883 -u 'sac/admin' -P '<your-password>' -t 'demo/#' -q 1 -v mosquitto_pub -h <vm-ip> -p 1883 -u 'sac/admin' -P '<your-password>' -t demo/hello -q 1 -m 'hi'Two things to know before you test
Use QoS 1 where delivery matters. BifroMQ routes messages through an asynchronous subscription table. A QoS 0 publish that arrives before a subscription has fully propagated is dropped silently.
That behaviour is correct MQTT, because QoS 0 is explicitly best effort with no delivery guarantee. On a freshly created subscription, however, it is very noticeable. Every example in this guide uses -q 1.
Publisher and subscriber must share a tenant. The tenant is the part of the username before the slash. Two clients in different tenants cannot see each other's messages, and neither client gets an error. You simply get silence, which is easy to mistake for a broken broker.
With the single credential this image ships, both ends use sac/admin, so the issue does not arise. It becomes relevant as soon as you add tenants of your own.
Confirm authentication is actually enforced
Run these checks once. An MQTT broker that silently accepts everything looks identical to a working one until it matters. Both of these connection attempts must be refused:
U=$(sudo grep '^BIFROMQ_MQTT_USERNAME=' /root/bifromq-credentials.txt | cut -d= -f2-) if mosquitto_pub -h 127.0.0.1 -p 1883 -u "$U" -P 'wrong-password' \
-t demo/hello -q 1 -m nope 2>/dev/null; then echo 'PROBLEM: a wrong password was accepted' else echo 'Wrong password correctly rejected' fi if mosquitto_pub -h 127.0.0.1 -p 1883 -t demo/hello -q 1 -m nope 2>/dev/null; then echo 'PROBLEM: an anonymous connection was accepted' else echo 'Anonymous connection correctly rejected' fiExpected output:
Wrong password correctly rejected Anonymous connection correctly rejectedThe administrative API
BifroMQ has no web console or CLI. Management is through an HTTP API. That API performs no authentication of its own, and it can publish to any topic and terminate any session. For that reason, this image binds it to loopback and enforces that with a firewall rule. The plugin metrics endpoint on 9090 is treated the same way.
Check broker health locally:
curl -s http://127.0.0.1:8091/clusterTo reach the API from your own machine, tunnel over SSH rather than opening the port:
ssh -L 8091:127.0.0.1:8091 ubuntu@<vm-ip>Then browse to http://127.0.0.1:8091/cluster on your own machine. Do not add a security group rule for 8091 or 9090.
Where everything lives
These are the key components and their paths:
- Distribution: /opt/bifromq/current
- Configuration: /opt/bifromq/current/conf/standalone.yml
- JVM and runtime settings: /etc/default/bifromq
- Data (RocksDB stores): /var/lib/bifromq/
- Logs: /var/log/bifromq/
- Credential store: /etc/bifromq/mqtt-users
- Your credentials: /root/bifromq-credentials.txt
- Broker unit: /etc/systemd/system/bifromq.service
- Authentication service: /etc/systemd/system/bifromq-authd.service
Logs are verbose and split by level. Start with these commands:
sudo tail -n 20 /var/log/bifromq/info.logChanging the MQTT password
Credentials live in /etc/bifromq/mqtt-users, one record per line, as username:salt:hash:tenant:user. The password is stored as a salted SHA-256 digest, never in plain text.
To rotate the password, generate a new salt and digest and replace the record:
NEWPW='<new-password>'
SALT=$(openssl rand -hex 16)
DIGEST=$(SALT_HEX="$SALT" PW="$NEWPW" python3 -c " import binascii, hashlib, os print(hashlib.sha256(binascii.unhexlify(os.environ['SALT_HEX']) + os.environ['PW'].encode()).hexdigest()) ") printf 'sac/admin:%s:%s:sac:admin\n' "$SALT" "$DIGEST" | \
sudo tee /etc/bifromq/mqtt-users > /dev/null
sudo chown root:bifromq /etc/bifromq/mqtt-users
sudo chmod 640 /etc/bifromq/mqtt-users echo 'Password rotated'The change takes effect on the next connection. The file is re-read on every request, so no restart is needed. Update /root/bifromq-credentials.txt too if you want it to stay accurate.
To add a second tenant, append another record with a different username and tenant, for example acme/sensor:...:acme:sensor.
Scaling up
The JVM is deliberately sized to fit a small instance. BifroMQ's own start script would otherwise claim about 70% of system memory as heap, plus a further 20% as direct memory. That does not fit on a small instance once RocksDB's off-heap usage is included.
If you move to a larger instance type, raise the heap to match by editing JVM_HEAP_OPTS in /etc/default/bifromq. Keep -XX:MaxDirectMemorySize set explicitly. If you remove it, direct memory silently defaults to the maximum heap size and doubles the footprint.
Leave headroom for RocksDB, which allocates outside the heap. Restart the broker with sudo systemctl restart bifromq.service.
Enabling TLS
The image ships without a certificate, so MQTT over TLS is disabled. To enable it, follow these steps:
- Place your certificate and key on the instance.
- Set
tlsListener.enable: truewith thecertFileandkeyFilepaths undermqttServiceConfig.server.tlsListenerin/opt/bifromq/current/conf/standalone.yml. - Restart the broker.
- Open TCP 8883 in the security group to your client networks.
Clients then connect with --cafile against port 8883. Prefer TLS for anything crossing an untrusted network, because plain MQTT sends credentials in the clear.
Security notes
- Keep 1883 scoped in the security group to the networks your devices actually use.
- Never open 8091 or 9090. Both are unauthenticated. Use the SSH tunnel in Step 6.
- Rotate the shipped password if the instance will be long-lived (Step 8).
- Use TLS (Step 10) for anything crossing an untrusted network.
- Patch regularly. Unattended security upgrades are enabled. Reboot periodically to pick up kernel updates.
Licensing
Apache BifroMQ is licensed under Apache-2.0 and is free to use. BifroMQ is currently undergoing incubation at the Apache Software Foundation. Incubation status does not necessarily reflect the completeness or stability of the code, and it indicates that the project has yet to be fully endorsed by the ASF.
SecureAnyCloud provides the packaging, hardening and commercial support separately. Contact sales@secureanycloud.com.
Deploy on AWS
Launch Apache BifroMQ (Incubating) MQTT Broker on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com