MQTT Keep Alive Explained: What It Is, How to Tune It, and Why Clients Disconnect
Your MQTT client connects, publishes a few readings, then goes quiet for five minutes. The broker marks it offline. When it tries to publish again, the connection is dead. This is the keep alive: the mechanism MQTT uses to tell the broker "I'm still here" when there's nothing to say.
This guide explains the MQTT keep alive from the wire up: the field in the CONNECT packet, the 1.5× timeout rule, PINGREQ and PINGRESP, how Last Will interacts with it, and how to pick the right interval for battery-powered sensors, industrial controllers, and laptops on flaky Wi-Fi.
What the Keep Alive Actually Is
The keep alive is a single 16-bit number in the CONNECT packet, the packet every client sends first when it connects. It's measured in seconds, and it tells the broker the maximum time it can wait with zero traffic before the client is presumed dead.
CONNECT packet (simplified)
┌──────────────┬───────────────┬───────────────┐
│ Protocol │ Flags │ Keep Alive │
│ Name "MQTT" │ (clean start │ (2 bytes, │
│ │ etc.) │ seconds) │
└──────────────┴───────────────┴───────────────┘
Example: 00 3C = 60 seconds keep alive
A keep alive of 60 means: the client must send some control packet at least every 60 seconds, and if the broker receives nothing from it for 1.5 × 60 = 90 seconds, it closes the connection as if the network had failed.
The critical detail: the broker counts from the last packet of any kind: a publish, a subscribe, a PINGREQ, anything. Traffic resets the timer.
The 1.5× Rule
The client has to send a packet within every keep alive interval, but the spec only obliges the broker to disconnect it after 1.5× the keep alive with no packet at all (MQTT 3.1.1 and 5.0, section 3.1.2.10):
Keep alive = 60 s
Broker's patience = 60 × 1.5 = 90 s
t = 0 Last packet received
t = 60 s Keep alive elapses; the client should already have sent something
t = 90 s Still nothing → broker closes the connection
The spec states the 1.5× allowance without giving a reason. In practice it gives a client that is a few seconds late, for example because of network jitter, some slack before it is declared dead. A broker is also allowed to drop a client it considers inactive at any time, whatever keep alive value the client sent.
Practical takeaway: the spec only requires the client to send some packet within each keep alive interval. Sending the PINGREQ before the full interval has elapsed, for example at 50–75% of it when there is no other traffic, leaves margin for network jitter. That is common advice, not a spec requirement.
PINGREQ and PINGRESP: The Heartbeat
When a client has nothing to publish and the keep alive is approaching, it sends a 2-byte PINGREQ packet. The broker must answer with a 2-byte PINGRESP. The PINGREQ counts as traffic for the broker's timer, and the PINGRESP tells the client the broker and the network are still there.
Client → Broker: PINGREQ (0xC0 0x00)
Broker → Client: PINGRESP (0xD0 0x00)
Both packets are 2 bytes. No payload.
Three failure modes to know:
- Broker doesn't reply to PINGREQ: the spec says the client SHOULD close the connection if no PINGRESP arrives within a reasonable time, then reconnect.
- Client never pings: the broker's 1.5× timer expires and the broker closes the connection.
- Network drops silently: no FIN, no RST, just silence (common on cellular). Keep alive is the only thing that detects this at the MQTT layer.
The last one is the real reason keep alive exists. TCP keepalive is off by default on most stacks, and on Linux the default idle time before the first probe is 2 hours (net.ipv4.tcp_keepalive_time = 7200 s). MQTT's keep alive gives you application-level detection within about 1.5× the interval you choose.
Keep Alive and Last Will
Keep alive and Last Will work together. When the broker's 1.5× timer expires, it:
- Closes the TCP connection,
- Publishes the client's Last Will message if one was set (in MQTT 5, after the Will Delay Interval, if the client set one),
- Keeps or discards the session according to the session settings (clean session in MQTT 3.1.1, Session Expiry Interval in MQTT 5).
This is the standard "device went offline" signal in industrial telemetry: the will message lands on a status topic, and the monitoring dashboard flips the device to OFFLINE. Without a keep alive, a device that dies silently can look online until TCP eventually notices.
Important nuance: a client that disconnects gracefully (sends DISCONNECT) does not trigger the will, except that an MQTT 5 client can ask for it with reason code 0x04. The will fires on an abnormal close, which is exactly what the keep alive timer detects. In MQTT 5 the broker may also reply with a DISCONNECT carrying reason code 0x8D, "Keep Alive timeout", and a broker can override the client's value with a Server Keep Alive property in the CONNACK, which the client must then use.
What Happens When the Keep Alive Is 0
A keep alive of 0 means "no keep alive." The broker is not required to time the client out for inactivity, and the client does not have to send packets on any schedule. The connection lives until the TCP layer or the broker's own limits (if any) kill it.
This is a legitimate choice for:
- Long-lived gateways on stable networks that publish continuously anyway,
- Local, trusted MQTT where the broker and client are on the same LAN,
- Fixed-line industrial links where TCP timeouts are already acceptable.
It's a poor choice for cellular IoT and anything battery-powered, where silent disconnects are common and a dead device should be detectable.
Choosing the Right Keep Alive Interval
There's no universal value. The right one depends on how often your device talks and how fast you need to know it died. The values below are starting points, not measured recommendations.
| Scenario | Suggested Keep Alive | Why |
|---|---|---|
| Battery sensor publishing every 5 min | 30–60 s | Pings between publishes; the broker disconnects it at most 1.5 × the keep alive (45–90 s) after its last packet |
| Industrial PLC / gateway | 15–60 s | Fast detection of plant-floor link drops |
| Phone / laptop app | 60–120 s | Moderate interval: detection within a couple of minutes without constant pinging |
| WebSocket MQTT | 30–60 s | Similar starting point as other clients; adjust if a proxy or load balancer in front of the broker closes idle connections |
Two rules of thumb:
- A keep alive longer than your publish gap is harmless. Every publish resets the broker's timer, so a device that publishes every 30 seconds can safely use a keep alive of 60. The risk runs the other way: if the keep alive is shorter than your publish gap and the client does not send PINGREQ itself, the broker disconnects it after 1.5× the keep alive. If you publish every 10 minutes with a keep alive of 60 seconds, the client library must ping in between.
- Don't set it needlessly small on battery power. Every PINGREQ wakes the radio. Pick the longest keep alive that still gives you acceptable offline detection.
Common bug: devices that publish rarely (hourly) with a keep alive of 60 seconds usually work, because most clients send PINGREQ automatically. But clients with broken ping implementations silently get disconnected by the broker's 1.5× timer. If you see devices flapping on and off, check the client's ping behavior first.
How to Debug Keep Alive Problems
When clients drop unexpectedly, the fastest way to see what's happening is to watch the packets at the broker:
- Enable broker protocol logging: mosquitto with
-v, or the equivalent in your broker, prints every CONNECT, PINGREQ, PINGRESP, and disconnect with a reason. - Look for the disconnect reason: MQTT 5 brokers can send a DISCONNECT with reason code 0x8D ("Keep Alive timeout"). With MQTT 3.1.1 the broker just closes the socket, so check the broker log.
- Time the gaps: if packets stop arriving well before the keep alive, the problem is the network path, not the setting.
- Test with a GUI client: an MQTT explorer that shows connection state and protocol activity makes the ping/pong rhythm visible instead of guessable.
A pattern that looks like a keep-alive bug but usually isn't: clients disconnecting at exactly the same time every day. That's almost always a broker-side idle timeout, a firewall rule, or a NAT session timeout, not MQTT's keep alive. Corporate firewalls and carrier NAT commonly drop idle TCP sessions at 5 or 30 minutes regardless of what MQTT wants.
Watch MQTT Connections Live
MQTT Explorer for macOS shows connection state, protocol traffic, and retained data in real time. Connect to any broker and see keep alives working, without a terminal.
Explore MQTT Explorer →