Why smart home devices should work without the internet, and how to design one that does

●1 ●6 ●13
calendar_today ago • schedule7 min read

The most upvoted post on Hacker News when I read it on 11 November, with 1,105 points, was Spencer Kittleson's "Smart home devices should work without the internet". His first rule is one sentence: "Nothing core in my house depends on a vendor's server."

The post, and the readers he quotes in it, list things that broke when that rule was ignored. A fridge showed a TLS certificate error on its own screen. A TV could not switch HDMI inputs while it was offline. A meat thermometer stopped working when the company behind it closed, even though the hardware was fine.

I want to read this as a system design problem rather than a gadget problem, because the same mistake turns up in backend systems all the time. An action that should be simple and local quietly ends up with someone else's server on its critical path, and nobody notices until that server goes away.

Where the request for a light actually goes

Take a cloud-only smart switch. You tap the button in the vendor's app. The phone sends the command to the vendor's cloud, and the cloud sends it back down to the switch over a connection the switch keeps open to it. The light is three metres from you, but the command went to a data centre and came back.

Count what has to be working for the light to turn on:

  1. your home internet connection,
  2. the vendor's servers,
  3. the vendor's API, in the version your app expects,
  4. the vendor itself, still in business and still running the service.

None of these has anything to do with sending current to a bulb. Each one is a dependency you cannot fix yourself, sitting on the path of the most basic thing the device does. In a backend you would call this a hidden critical dependency, and you would want to remove it.

Kittleson lists exactly this failure in his own house: "My Nest integration worked until Google broke the API." Nothing in his house changed. A server he did not own changed, and a feature stopped working.

The design: keep every core path inside the house

Here is the shape of the home his post describes. I drew it from the parts list and the architecture section of the post.

A home that keeps working when the internet is down

The pieces are ordinary:

  • Devices are ESP8266 and ESP32 boards running Tasmota, open firmware that speaks MQTT. The garage has a reed switch on the door, and the sprinkler controller is an ESP8266 with a relay.
  • The MQTT broker runs on the home router, a GL.iNet Flint 2 running OpenWrt. Every device publishes to it and subscribes to it.
  • Node-RED holds the automation rules, and Grafana shows dashboards and history. Both run on a Raspberry Pi 4.
  • A wall-mounted tablet shows the dashboard. He writes that it "replaced more apps than anything else."
  • The extras sit outside: Alexa, which he calls "just another way to flip a switch", Telegram for notifications, and Tailscale on the router for remote access.

The important design choice is publish and subscribe. The garage sensor publishes "the door is closed" to a topic on the broker. It does not know or care who is listening. Node-RED listens and decides what to do. The tablet listens and shows it. Later you can add another listener, such as a logger, without touching the sensor at all. This is the same reason backend teams put a message broker between services: the producer and the consumers can change independently.

Now look at the dashed links in the figure. They are the only ones that cross the internet. If the internet goes down, Alexa stops and the phone alerts stop. The sensors, the broker, the rules and the tablet keep talking to each other, because every hop between them stays on the home network.

The test: unplug the internet cable

The simplest way to check a design like this is to imagine the internet cable unplugged for a day and walk through every feature. Here is the same light switch, built both ways.

Cloud-only or local-first: what still works offline

Two rows deserve a comment.

Control from outside the house works in both designs. Local-first does not mean offline-only. Kittleson reaches his house remotely through Tailscale on the router, and he keeps the individual devices off his Tailscale network. So remote control still needs the internet, as it must, but it is an extra path on top of a house that works without it, not the only path.

Setup effort is the honest cost. You run a broker, you keep firmware up to date, and you are the support team. One reader in the thread estimated that local-first hardware costs about 2.5 times more. For a smart lamp in a spare room, the cloud version may be fine. For heating, water, locks and the main lights, I would not accept a design where a vendor outage leaves the house dark.

This is the same exercise as asking, in a backend design review, "what happens to checkout if the recommendations service is down?" The answer should be that checkout still works and the page shows no recommendations. Sort every feature into core and extra, and make sure each core feature has a path that does not need anything you cannot control.

How the house knows what is alive, without a cloud

A cloud dashboard knows a device is offline because the device stopped calling home. Once you remove the cloud, something local has to do that job. MQTT has two features for this, and they are worth knowing even if you never touch a smart home.

How the house knows a device is alive, without any cloud

The will message. When the sensor connects, it leaves a message with the broker to publish on its behalf if it disappears. Tasmota uses the topic tele/<topic>/LWT with the payloads Online and Offline. The MQTT 5.0 specification says the will message "MUST be published after the Network Connection is subsequently closed", unless the client sent a normal DISCONNECT first. A clean shutdown says goodbye. A power cut or a crash does not, so the broker announces Offline for it.

Keep alive. The sensor promises to send something at least every 30 seconds in this example. If the broker hears nothing for "one and a half times the Keep Alive time period", which is 45 seconds here, it closes the connection, and that triggers the will message. This is a heartbeat with a timeout, the same idea a load balancer uses with health checks.

Retained messages. When the sensor publishes the door state with the RETAIN flag set to 1, the broker "MUST replace any existing retained message for this topic and store the Application Message." When a new client subscribes, the broker sends it the stored value straight away. So when the wall tablet reboots at 3 a.m., it shows "closed" at once. Without retain, it would show nothing until the door next moved, which for a garage could be hours.

Put together, these give you liveness and last known state on the local network, with no server outside the house involved.

Where even this design falls short

Two points from the post and the thread keep it honest.

First, readers proposed a stricter standard: "A switch that only sends a request to the automation fails." By that standard, if the Raspberry Pi dies, a wall switch that only publishes to Node-RED does nothing. Kittleson admits his own sprinkler controller would fail it. The strongest design keeps a direct physical path: the switch still controls the light even when the hub is gone. That links to one of his own rules, which is to never remove the old control while you add the new one. In his house someone taped a wall switch on so that a smart bulb kept its power, which is that rule being broken in real life.

Second, local does not mean free of failure. The broker on the router is now a single point of failure inside the house. That is a much better place for it than a vendor's data centre, because you can see it, restart it and replace it. But it still deserves a backup of its configuration and a plan for what happens when it reboots.

A checklist you can use for any system

These steps apply to a home, an app or a company's internal tools:

  1. List the core actions, the ones that must work on a bad day.
  2. Trace the path each one takes, hop by hop, and write down every system it touches.
  3. Mark each hop you do not control. Each mark is something you must be able to lose.
  4. Keep the last known state close to where it is read, as retained messages do.
  5. Detect death actively with heartbeats and timeouts, rather than waiting for a user to notice.
  6. Keep a manual fallback for the things people touch every day.

The name for the habit behind this checklist is graceful degradation: when a part fails, the system loses the extras and keeps the core. I keep a short plain-English explanation of it, with examples from backend systems, on systemdesign.academy: https://www.systemdesign.academy/glossary/graceful-degradation?utm_source=coderlegion&utm_medium=article&utm_content=local-first-home-1111

Sources

The figures are my own drawings. The topics and values in the MQTT figure are examples, apart from the Tasmota LWT topic pattern.

🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelski - Mar 19

The Sovereign Vault — A Comprehensive Guide to Protocol-Driven AI

Ken W. Algerverified - Jun 4

How I Built a React Portfolio in 7 Days That Landed ₹1.2L in Freelance Work

Dharanidharan - Feb 9

A Textile Mill's Dyehouse Ramp Curve: Get One Step Wrong and the Sheet Belongs to the Master

aiomniu - Oct 18

Short links that outlive the vendor: a newsroom URL shortener on WordPress and AWS

jaafarabazid - Nov 10
chevron_left
589 Points • 20 Badges
5Posts
11Comments
3Connections
Teaching how real systems and AI are built: system design, AI engineering, agentic AI. Hands-on lessons at systemdesign.academy

Related Jobs

Commenters (This Week)

29 comments
6 comments
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!