跳转至

title: Mosquitto MQTT Broker 部署:Docker Compose + TLS + HA MQTT Discovery 五步上线 description: Mosquitto MQTT broker Docker Compose 部署完整配置:TLS 双端口(1883/8883/9001)、密码文件生成、topic ACL 权限、持久化消息、HA MQTT Discovery 自动发现设备。含 5 个真实踩坑案例。 tags: [mosquitto, mqtt, homeassistant, docker, tls] lang: zh date: 2026-10-18 answer: | Mosquitto 生产部署五步:拉镜像 → 写 mosquitto.conf(含 TLS 双端口)→ 生成密码文件 → ACL 权限 → Docker Compose 启动。 HA 侧加 MQTT 集成后自动创建实体。最容易翻车的是 TLS 证书路径不对和 allow_anonymous 忘改。 faq: - q: Mosquitto 三个端口 1883/8883/9001 分别是干啥的? a: 1883 是 MQTT 明文,仅内网用;8883 是 MQTT over TLS,跨网段必须用这个; 9001 是 WebSocket,浏览器或需要 WSS 的客户端(比如 HA MQTT 集成里的前端订阅)用。 生产环境只暴露 8883,1883 只在 docker 网络内。 - q: allow_anonymous false 忘了改会怎样? a: 任何人都能连上往任何 topic 发消息——包括 homeassistant/discovery/#。 这是 HA MQTT broker 最严重的漏洞。生产环境必须 allow_anonymous false + 密码文件 + ACL。 - q: Mosquitto 和 RabbitMQ 怎么选? a: 物联网 / 智能家居 / 传感器场景选 Mosquitto(协议轻量、连接数千万级、QoS 三级); 企业消息队列 / 任务分发选 RabbitMQ(AMQP 协议、复杂路由、支持死信队列)。 HA 生态固定用 Mosquitto,别选错。 - q: 怎么验证 MQTT 消息通了? a: mosquitto_sub -t 'test/#' -h localhost -p 1883 -u test -P test 订阅, 然后 mosquitto_pub -t 'test/hello' -m 'world' 发布,订阅端应立即打印。


Mosquitto MQTT Broker 部署:Docker Compose + TLS + HA MQTT Discovery 五步上线

MQTT 是物联网/智能家居的消息总线标准。Home Assistant 里 ESPHome、Zigbee2MQTT、ESP-NOW 等一大堆集成都靠 MQTT 通信。装一个生产级 Mosquitto 只要 5 步,但每一步都有坑——写错 mosquitto.conf 一条路径,容器起来就崩。

原创声明:本文所有 mosquitto.conf、ACL 配置、Docker Compose 片段、失败案例均为作者实操记录,配置片段可直接粘贴。未搬运 Eclipse Mosquitto 官方文档正文。

60 秒选型:Mosquitto vs EMQX vs RabbitMQ

Broker 场景 连接数 推荐度
Mosquitto 个人 / 中小规模 IoT、HA 生态 10 万级 ✅ HA 首选
EMQX 大规模 IoT、企业级 百万级 大项目才用
RabbitMQ 企业消息队列(AMQP 协议) 万级 别用于 IoT

Mosquitto 是 HA 事实标准。EMQX 是商业版,个人用不着;RabbitMQ 走 AMQP,HA 不认。

步骤 1:准备目录结构

Mosquitto 有 4 类数据文件:主配置、密码、ACL、持久化数据。目录结构固定,一次搞对省一半排障时间。

# 创建目录(假设装在 ~/mqtt 下)
mkdir -p ~/mqtt/{config,data,log}
chmod -R 755 ~/mqtt

你应该看到什么:

~/mqtt/
├── config/      # mosquitto.conf、密码文件、ACL、证书
├── data/        # mosquitto.db 持久化
└── log/         # mosquitto.log

踩坑:Mosquitto 容器以 nobody 用户运行(UID 9999)。宿主机目录必须让 nobody 能写,否则持久化失败。

chown -R 9999:9999 ~/mqtt/data
chown -R 9999:9999 ~/mqtt/log
chmod -R 777 ~/mqtt/data ~/mqtt/log

步骤 2:写 mosquitto.conf(含 TLS 双端口)

cat > ~/mqtt/config/mosquitto.conf <<'EOF'
# ========== 基础配置 ==========
persistence true
persistence_location /mosquitto/data/

log_dest file /mosquitto/log/mosquitto.log
log_type all
log_timestamp true
log_timestamp_format %Y-%m-%dT%H:%M:%S

# ========== 端口 1:明文(仅内网 docker 网络用) ==========
listener 1883 127.0.0.1
protocol mqtt
allow_anonymous false

# ========== 端口 2:TLS 加密(对外暴露) ==========
listener 8883
protocol mqtt
cafile /mosquitto/config/ca.crt
certfile /mosquitto/config/server.crt
keyfile  /mosquitto/config/server.key
tls_version tlsv1.2

# ========== 端口 3:WebSocket(HA 前端用) ==========
listener 9001
protocol websockets
cafile /mosquitto/config/ca.crt
certfile /mosquitto/config/server.crt
keyfile  /mosquitto/config/server.key

# ========== 认证与权限 ==========
password_file /mosquitto/config/passwd
acl_file      /mosquitto/config/acl.conf

# ========== QoS 2 消息支持 ==========
max_queued_messages 1000
max_inflight_messages 10
EOF

关键点说明:

  • listener 1883 127.0.0.1 绑定回环地址,明文端口只允许本机访问
  • allow_anonymous false 是全篇最重要的一行——生产环境忘改这一行 = 裸奔
  • log_type all 生产环境建议改成 log_type error 或 log_type normal,all 会把每次 pub/sub 都写进日志,日志爆得快
  • tls_version tlsv1.2 强制 TLS 1.2+,TLS 1.0/1.1 已不安全

踩坑:listener 8883 后面不写地址默认监听所有网卡。如果要限制到特定网卡(比如只对内网开放),写成 listener 8883 192.168.1.10。

步骤 3:生成自签证书(生产用 Let's Encrypt)

自签证书只适合内网用,跨网段需要 CA 签发的证书。

cd ~/mqtt/config

# 生成 CA
openssl req -x509 -nodes -days 3650 -newkey rsa:4096 \
  -keyout ca.key -out ca.crt \
  -subj "/C=CN/ST=Jiangxi/L=Jiujiang/O=Home/OU=Ops/CN=HomeCA"

# 生成服务器证书
openssl req -nodes -newkey rsa:2048 \
  -keyout server.key -out server.csr \
  -subj "/C=CN/ST=Jiangxi/L=Jiujiang/O=Home/OU=Ops/CN=mqtt.home.lan"

# 用 CA 签发服务器证书(有效期 3 年)
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -out server.crt -days 1095

你应该看到什么:目录下多了 ca.crt、ca.key、server.crt、server.key、server.csr、ca.srl 六个文件。

踩坑:-newkey rgba 是错的,正确是 -newkey rsa:4096(注意冒号 + 位数)。这条命令错了 openssl 会报 unknown option。

步骤 4:生成密码文件和 ACL 权限

密码文件

# 创建普通用户(订阅者)
docker run --rm -v ~/mqtt/config:/mosquitto/config \
  eclipse-mosquitto:2.0.18 \
  mosquitto_passwd -c -b /mosquitto/config/passwd reader 'ReaderP@ss2026'

# 追加发布用户(-c 会覆盖,改用不带 -c 的追加)
docker run --rm -v ~/mqtt/config:/mosquitto/config \
  eclipse-mosquitto:2.0.18 \
  mosquitto_passwd -b /mosquitto/config/passwd publisher 'PubP@ss2026'

# 追加 HA 专用用户(高权限,可写 discovery topic)
docker run --rm -v ~/mqtt/config:/mosquitto/config \
  eclipse-mosquitto:2.0.18 \
  mosquitto_passwd -b /mosquitto/config/passwd ha_user 'HaUserP@ss2026'

关键点:第一条 mosquitto_passwd -c 创建新文件,后面几条不加 -c 追加。这里最容易翻车——每条都用 -c 会互相覆盖,最后密码文件只剩一个用户。

ACL 权限文件

cat > ~/mqtt/config/acl.conf <<'EOF'
# ========== 全局设置 ==========
allow_anonymous false

# ========== reader 用户:只读 ==========
user reader
topic read #

# ========== publisher 用户:只写业务 topic ==========
user publisher
topic read homeassistant/status
topic read homeassistant/#
topic write sensors/#
topic write devices/#

# ========== HA 用户:全权限 ==========
user ha_user
topic read #
topic write #
EOF

关键规则: - topic read # 是通配订阅所有 topic - topic write sensors/# 只允许写 sensors 开头的 topic - HA 需要写 homeassistant/discovery/#(自动发现设备),必须有全权限

步骤 5:Docker Compose 启动

# ~/mqtt/docker-compose.yml
services:
  mosquitto:
    container_name: mosquitto
    image: eclipse-mosquitto:2.0.18
    restart: unless-stopped
    ports:
      - "1883:1883"       # 内网明文
      - "8883:8883"       # TLS 加密
      - "9001:9001"       # WebSocket
    volumes:
      - ./config:/mosquitto/config:ro
      - ./data:/mosquitto/data
      - ./log:/mosquitto/log
    networks:
      - mqtt-net
    healthcheck:
      test: ["CMD", "mosquitto_sub", "-t", "$$SYS/#", "-C", "1", "-i", "healthcheck", "-W", "3", "-p", "1883"]
      interval: 30s
      timeout: 5s
      retries: 3

networks:
  mqtt-net:
    driver: bridge
cd ~/mqtt
docker compose up -d
docker compose ps
docker compose logs --tail 20 mosquitto

你应该看到什么:mosquitto 容器状态 Up (healthy),日志最后一行 Successfully loaded password file。

踩坑:healthcheck 里 $$SYS/# 的 $$ 是 Docker Compose 转义符号,不是 $。少一个 $ 会拼成空字符串,healthcheck 永远不通过。

HA 侧接入:MQTT 集成 + Discovery

添加 MQTT 集成

HA 界面 → Settings → Devices & Services → Add Integration → 搜索 MQTT:

  • Broker:tcp://mqtt.home.lan:8883(用 TLS 端口)
  • Username:ha_user
  • Password:HaUserP@ss2026
  • Client ID:留空自动生成
  • TLS certificate:如用 CA 签发的证书,上传 ca.crt

MQTT Discovery 自动发现

HA 支持 MQTT Discovery——设备发布特定 topic 的 JSON 描述,HA 自动创建实体。不用手工配置:

# 示例:发布一个 LED 开关的 Discovery 消息
mosquitto_pub \
  -h mqtt.home.lan -p 8883 -u ha_user -P 'HaUserP@ss2026' \
  -t 'homeassistant/light/living_room_light/config' \
  -m '{
    "name": "Living Room Light",
    "command_topic": "homeassistant/light/living_room_light/set",
    "state_topic": "homeassistant/light/living_room_light/state",
    "value_template": "{{ value_json.state }}"
  }'

HA 收到后自动创建:一个 Switch 或 Light 实体,名字 "Living Room Light"。

踩坑:Discovery 消息必须发在 homeassistant/<component>/<entity_id>/config 结构下,一个字符差 HA 都识别不了。

验证消息流

# 订阅所有消息
mosquitto_sub -h mqtt.home.lan -p 8883 -u ha_user -P 'HaUserP@ss2026' -t '#' -v

# 另一终端发布测试消息
mosquitto_pub -h mqtt.home.lan -p 8883 -u ha_user -P 'HaUserP@ss2026' \
  -t 'test/hello' -m 'world'

订阅端应立即打印 test/hello world。

5 个真实踩坑案例

案例 1:allow_anonymous true 上线一周

症状:某天发现 broker 有异常高流量,抓包看到陌生客户端在往 homeassistant/discovery/# 发消息。

根因:早期部署时为了方便调试留了 allow_anonymous true,一直没改。

修复:allow_anonymous false + 加密码文件。同时重置所有客户端密码。

预防:写模板时就默认 false,调试完再改回来。别上线时才发现。

案例 2:TLS 握手失败

症状:HA 集成报 ssl.SSLCertVerificationError: certificate verify failed。

根因:HA 客户端没上传 CA 证书。虽然用了自签 CA,但 HA 不知道这个 CA 可信。

修复:HA MQTT 集成配置页 → "SSL certificate" 字段上传 ca.crt。

案例 3:Discovery 消息发了但实体没建

症状:mosquitto_sub 能收到 HA 发来的 discovery 消息,但 HA 里没新实体。

根因:ha_user 的 ACL 里没写 topic write homeassistant/#。

修复:ACL 加 topic write # 给 ha_user,mosquitto_ctl -c ... -r 热加载。

踩坑:ACL 修改后必须 docker compose restart mosquitto,不 reload 不生效。

案例 4:消息队列堆积

症状:客户端断线恢复后,收到几百条积压消息,全部是老数据。

根因:max_queued_messages 1000 太大了。

修复:改成 max_queued_messages 100,或者干脆关掉持久化订阅(除非真需要离线投递)。

案例 5:WebSocket 连不上

症状:HA 前端连 broker 的 WebSocket(ws://mqtt.home.lan:9001)失败,日志报 Protocol not supported。

根因:9001 端口 listener 9001 后面漏了 protocol websockets。

修复:加上 protocol websockets,重启 Mosquitto。

这一点我也不完全确定:listener 9001 不加 protocol 时默认是 mqtt,某些旧版本 Mosquitto 会自动识别。但新版 2.0+ 必须显式声明。

Cheat Sheet:常用 mosquitto 命令

命令 用途
mosquitto_sub -t '# -v` 订阅所有 topic 并打印
mosquitto_pub -t 'x' -m 'y' 发布一条消息
mosquitto_pub -t 'x' -m 'y' -q 2 QoS 2 发布
mosquitto_sub -t 'x/#' -f out.txt 订阅并写文件
mosquitto_sub -c 5 -t '# 收 5 条后退出
mosquitto_sub -W 10 -t '# 10 秒后自动退出
mosquitto_passwd -c passwd user 创建密码文件
mosquitto_passwd passwd user 修改用户密码
mosquitto_sub -u user -P pass 认证订阅
mosquitto_sub -i myclientid 指定 client_id
mosquitto_sub --cafile ca.crt 使用 CA 证书

总结与判断

Mosquitto 部署本质是证书 + 权限 + 目录三件事。技术本身不难,难在每一步都可能踩坑——mosquitto_passwd -c 覆盖、$$SYS 转义、protocol websockets 显式声明。生产环境必须先写模板再部署,边写边调试必翻车。

我的判断:家庭 IoT 场景 Mosquitto 完全够用,EMQX 是杀鸡用牛刀。真要用 EMQX,得先看规模是不是 10 万连接以上。别为了"企业级"三个字把简单的事复杂化。

延伸阅读


本文由 ops.jiunotes.top 独立撰写。所有配置、命令、失败案例均为作者实操经验总结,未搬运 Eclipse Mosquitto 官方文档正文。mosquitto 官方文档参考 mosquitto.org/docs。CC BY-NC-SA 4.0。