Tutorials

ESPHome MQTT Configuration Guide: Discovery, Birth/Will and External Brokers

Configure the ESPHome mqtt: component: broker and credentials, topic_prefix, Home Assistant discovery, birth and will messages, TLS, on_message and testing.

Updated · 9 min read

ESPHome MQTT Configuration Guide: Discovery, Birth/Will and External Brokers

ESPHome talks MQTT through one block of YAML. Add mqtt: with a broker address and every sensor, switch and light on the device publishes its state and accepts commands on predictable topics, with no C++ to write. It works with Home Assistant through MQTT discovery, and just as well without Home Assistant at all, feeding Node-RED, Grafana or your own backend from any broker.

This guide covers a complete configuration against an external broker, the topic layout ESPHome generates, discovery, birth and will messages, TLS, custom subscriptions, and how to verify every part from the browser.

A minimal working configuration

livingroom.yaml
esphome:
  name: livingroom

esp32:
  board: esp32dev
  framework:
    type: esp-idf

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

logger:

ota:
  - platform: esphome
    password: !secret ota_password

mqtt:
  broker: broker.connectmq.com
  port: 1883
  username: !secret mqtt_user
  password: !secret mqtt_password
  client_id: esphome-livingroom
  topic_prefix: testmqtt/esphome/livingroom
  discovery: false

sensor:
  - platform: wifi_signal
    name: "WiFi Signal"
    update_interval: 30s
  - platform: uptime
    name: "Uptime"

switch:
  - platform: gpio
    pin: GPIO2
    name: "Relay"
    id: relay
secrets.yaml
wifi_ssid: "your-ssid"
wifi_password: "your-wifi-password"
ota_password: "change-me"
mqtt_user: "username"
mqtt_password: "password"

Flash it with esphome run livingroom.yaml. In the log you should see Connecting to MQTT... followed by MQTT Connected!.

Three settings are worth setting explicitly instead of relying on defaults:

  • topic_prefix defaults to the node name (livingroom). On a shared broker that collides with everyone else's living room. A unique prefix such as testmqtt/esphome/livingroom keeps your topics yours.
  • client_id must be unique per device. Two devices with the same ID disconnect each other in a loop. ESPHome generates one from the name and MAC if you leave it out, which is fine; just never copy a fixed one between YAML files.
  • discovery: false while testing on a public or shared broker, so you do not publish Home Assistant config messages to a namespace other people read. Turn it on when you use your own broker.

The topics ESPHome generates

Each entity gets a state topic, and controllable entities a command topic. The object ID is the entity name in lowercase with spaces replaced by underscores:

EntityTopicDirection
Availabilitytestmqtt/esphome/livingroom/statusdevice → broker (online / offline)
WiFi Signal sensortestmqtt/esphome/livingroom/sensor/wifi_signal/statedevice → broker
Uptime sensortestmqtt/esphome/livingroom/sensor/uptime/statedevice → broker
Relay switch statetestmqtt/esphome/livingroom/switch/relay/statedevice → broker (ON / OFF)
Relay switch commandtestmqtt/esphome/livingroom/switch/relay/commandbroker → device (ON / OFF / TOGGLE)
Logstestmqtt/esphome/livingroom/debugdevice → broker

Override any of them per entity with state_topic: and command_topic: when you need to fit an existing topic design. The log_topic: option controls the debug topic; set it to null to stop publishing logs over MQTT, which saves bandwidth on busy devices.

Birth, will and shutdown messages

ESPHome sends a birth message after connecting and registers a Last Will with the broker. By default both go to <topic_prefix>/status, retained, with the payloads online and offline. That is what Home Assistant uses for entity availability. You can customise them:

Custom availability
mqtt:
  # ...
  birth_message:
    topic: testmqtt/esphome/livingroom/availability
    payload: "connected"
    retain: true
  will_message:
    topic: testmqtt/esphome/livingroom/availability
    payload: "lost"
    retain: true
  shutdown_message:
    topic: testmqtt/esphome/livingroom/availability
    payload: "shutdown"
    retain: true
  keepalive: 30s

The will is published by the broker, not the device, roughly 1.5 × keepalive after the device goes silent. The shutdown message is sent by the device itself before a planned reboot or OTA update, so you can tell "rebooting on purpose" from "lost power". The timing is explained in MQTT Last Will, and retained flags in retained messages.

Home Assistant discovery

With discovery: true (the default), ESPHome publishes one retained config message per entity under <discovery_prefix>/<component>/<node>/<object_id>/config. Home Assistant's MQTT integration subscribes to that prefix and creates the entities automatically.

Discovery on your own broker
mqtt:
  broker: broker.connectmq.com
  port: 1883
  username: !secret mqtt_user
  password: !secret mqtt_password
  discovery: true
  discovery_prefix: homeassistant
  discovery_retain: true

Things to know:

  • The prefix must match. Home Assistant listens on homeassistant unless you changed it in the MQTT integration options. If you change one, change both.
  • Discovery messages are retained. If you rename or delete an entity, the old config stays on the broker. Clear it by publishing an empty retained message to the old config topic.
  • One broker for both. Home Assistant must connect to the same broker as the device. For the full Home Assistant side, read Home Assistant MQTT.

Subscribing and publishing from YAML

ESPHome can react to any topic, not just its own command topics, and publish arbitrary messages:

Custom subscriptions and publishes
mqtt:
  # ...
  on_message:
    - topic: testmqtt/esphome/livingroom/cmd/toggle
      qos: 1
      then:
        - switch.toggle: relay
  on_json_message:
    topic: testmqtt/esphome/livingroom/cmd/json
    then:
      - if:
          condition:
            lambda: 'return x["relay"] == "on";'
          then:
            - switch.turn_on: relay
          else:
            - switch.turn_off: relay

interval:
  - interval: 60s
    then:
      - mqtt.publish:
          topic: testmqtt/esphome/livingroom/heartbeat
          payload: "alive"
          qos: 0
          retain: false

on_json_message parses the payload with ArduinoJson and exposes it as x. Keep payloads small: very large messages may exceed the client's buffer on memory-constrained boards.

Using TLS with an external broker

On ESP32, add the broker's CA certificate and switch to port 8883:

TLS on ESP32
mqtt:
  broker: broker.connectmq.com
  port: 8883
  username: !secret mqtt_user
  password: !secret mqtt_password
  certificate_authority: |
    -----BEGIN CERTIFICATE-----
    ... root CA of your broker's certificate chain ...
    -----END CERTIFICATE-----

Use the root CA of the chain the broker presents, not the server certificate itself. The ESP-IDF framework is the recommended choice for MQTT over TLS on ESP32. ESP8266 support is much more limited (fingerprints only on the Arduino framework), so for TLS-critical devices choose an ESP32, or keep ESP8266 devices on a trusted local network. Our ESP32 TLS guide explains how to extract the right certificate with openssl s_client.

Test every part from the browser

  1. Open the online MQTT client, connect to broker.connectmq.com on port 8084 (path /mqtt) and subscribe to testmqtt/esphome/livingroom/#.
  2. You should see status = online (retained) and the wifi_signal and uptime states every 30 seconds.
  3. Publish ON to testmqtt/esphome/livingroom/switch/relay/command. The relay switches and switch/relay/state reports ON.
  4. Publish anything to testmqtt/esphome/livingroom/cmd/toggle to exercise on_message.
  5. Power the board off. After about 1.5 × keepalive the will sets status to offline. Reboot it from the dashboard instead and you will see the shutdown message first.

Watch your ESPHome node in the online client

If an entity does not appear on the expected topic, paste the topic and your filter into the topic matcher to check for a typo in the prefix.

Troubleshooting quick reference

Log messageLikely cause
MQTT Disconnected: TCP disconnected.Wrong host or port, firewall, TLS port used without certificate_authority
MQTT Disconnected: Not Authorized.Wrong username or password, or ACL denies the client
MQTT Disconnected: Identifier Rejected.Client ID rejected or duplicated
Device reboots after 15 minutesreboot_timeout reached because the broker is unreachable

For a step-by-step diagnosis, read ESPHome MQTT not connecting.

Configuring ESPHome for MQTT is mostly about naming: a unique topic prefix and client ID, discovery switched on only where Home Assistant lives, and availability topics you can trust. Get those right, verify state, command and will from the online client, and the same YAML moves from your test broker to production by changing a host name and a secret.

Frequently asked questions

Can I use ESPHome with MQTT and the native API at the same time?

Yes. The api: and mqtt: components are independent. Many setups keep the native API for Home Assistant and add MQTT so other systems (Node-RED, a data logger, a second site) can read the same sensors.

Why does my ESPHome device reboot every 15 minutes?

The mqtt: component has reboot_timeout, 15 minutes by default: if the broker cannot be reached for that long, the device restarts. Fix the connection, or set reboot_timeout: 0s to disable it on devices that must keep running offline.

What topics does ESPHome publish to?

By default <topic_prefix>/<component_type>/<object_id>/state, where topic_prefix is the node name and object_id is the entity name in lowercase with underscores. Controllable entities listen on the same path ending in /command.