Building an OpenBSD Home Automation Gateway with Zigbee2MQTT and openHAB
tailoring a silent, isolated IoT gateway using Zigbee2MQTT, Mosquitto, and openHAB on fanless x86 hardware

If you’ve been following my previous adventures in home automation, you know I’ve been sliding down the Zigbee rabbit hole for a while. It all started with getting my hands dirty tracking frames in Zigbee-Sniffing unter OpenBSD: Deep Dive mit dem TI CC2531. Out of curiosity, I got myself a Zigbee Coordinator and wrote about connecting it to an automation platform in Zigbee Home Automation auf OpenBSD: Sonoff Dongle-M und openHAB.
Recently, I picked up a batch of surplus thin clients that turned out to be the absolute perfect fit for a dedicated home controller. Here is how I built a robust, isolated smart home gateway using OpenBSD 7.9-snapshot, Zigbee2MQTT, Mosquitto, and openHAB.
The Hardware Stack
The foundation of this build is the Fujitsu Futro S9010n. These little machines are absolute gems for home servers. For under the price of a modern Raspberry Pi kit, you get a fully enclosed x86 machine equipped with:
- 20 GB RAM (Originally came with 8GB, but was able to make it work with 20GB, 32GB made it slow like hell)
- 64 GB SSD
- Fanless cooling (totally silent in a living room or utility closet)
- Dual native serial ports
inteldrm is enabled. Since this is run completely headless as a server, it doesn’t bother me, but keep it in mind during initial installation!To keep things secure, I locked this server into a dedicated IoT VLAN. Network communication out to the physical Zigbee mesh is handled over the network via a SONOFF Zigbee 3.0 USB Dongle Plus (Dongle-M), isolating untrusted endpoints entirely from my primary network lanes.
With the backbone ready, I loaded up an array of smart devices to manage:
- Power: A 4-Pack of SONOFF Zigbee Smart Plugs (great for mapping routers across the mesh).
- Climate: A SONOFF Smart Zigbee 3.0 Thermostat as well as SONOFF SNZB-02P Temperature & Humidity Sensors.
- Presence & Lighting: The microwave-radar-based SONOFF SNZB-06P Presence Sensor driving standard GU10 Smart Zigbee LED Bulbs and a matching Zigbee Deckenlampe.
Software Configuration
The architecture is layered sequentially: Nginx intercepts external traffic, authenticates requests, and reverse-proxies them downward to openHAB or Zigbee2MQTT, which interact using the Mosquitto MQTT broker. The base configurations are deployed via OpenVOX, with simple daily shell scripts handling automated state backups.
1. Zigbee2MQTT
The core Zigbee translation engine runs on port 8080. Note that we are passing the adapter connection via TCP to reach the isolated network segment where the Sonoff Dongle-M resides.
version: 5
homeassistant:
enabled: true
experimental_event_entities: true
legacy_action_sensor: true
frontend:
enabled: true
host: 127.0.0.1
base_url: /zigbee2mqtt
port: 8080
mqtt:
base_topic: zigbee2mqtt
server: mqtt://localhost
version: 5
serial:
port: tcp://sonoff.example.com:6638
baudrate: 115200
adapter: ember
rtscts: false
advanced:
log_level: info
log_directory: /var/log/zigbee2mqtt
log_file: zigbee2mqtt_%TIMESTAMP%.log
log_rotation: true
log_output:
- file
network_key:
# ... secret key ...2. Mosquitto & openHAB 5.1.3
Mosquitto requires zero wizardry; pull the package from the OpenBSD repositories, enable it via rcctl, and let it run.
For openHAB, installation is similarly straightforward. To prevent it from clashing with the Zigbee2MQTT frontend port, remember to map its listening port out of the box to 8081 within /etc/openhab.conf:
OPENHAB_HTTP_PORT=80813. Nginx Reverse Proxy
By routing traffic through Nginx, we can run openHAB securely at the root directory / while containing the raw Zigbee2MQTT administration panel safely behind an explicit HTTP basic authentication barrier at /zigbee2mqtt.
# MANAGED BY OpenHAB
server {
listen *:443 ssl;
listen [::]:443 ssl ipv6only=on;
server_name ha ha.example.com;
http2 off;
ssl_certificate /etc/puppetlabs/puppet/ssl/certs/ha.example.com.pem;
ssl_certificate_key /etc/puppetlabs/puppet/ssl/private_keys/ha.example.com.pem;
index index.html index.htm index.php;
access_log /var/www/logs/ssl-ha.example.com.access.log;
error_log /var/www/logs/ssl-ha.example.com.error.log;
location /zigbee2mqtt {
auth_basic "Home Automation Area";
auth_basic_user_file /conf/.htpasswd;
proxy_pass http://127.0.0.1:8080;
proxy_read_timeout 90s;
proxy_send_timeout 90s;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Proxy "";
client_body_buffer_size 128k;
client_max_body_size 8m;
proxy_buffers 4 32k;
proxy_redirect http:// https://;
proxy_set_header Connection "upgrade";
proxy_set_header Upgrade $http_upgrade;
}
location / {
proxy_pass http://127.0.0.1:8081;
proxy_read_timeout 90s;
proxy_send_timeout 90s;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Proxy "";
client_body_buffer_size 128k;
client_max_body_size 8m;
proxy_buffers 4 32k;
proxy_redirect http:// https://;
proxy_set_header Connection "upgrade";
proxy_set_header Content-Type $http_content_type;
proxy_set_header Upgrade $http_upgrade;
}
}Device Pairing & The OpenBSD Roadblock
Once Nginx was reloaded, pairing endpoints inside the Zigbee2MQTT dashboard worked flawlessly. You toggle joining mode via the UI, press the pairing buttons on the physical sensors, and they instantly populate the interface. The mapping engine compiles a gorgeous visualization of the entire interconnected hardware mesh

Zigbee network mesh
alongside live variable readouts

Zigbee devices dashboard
However, passing those recognized devices over to openHAB hit a massive structural roadblock.
The Home Assistant Binding Exception
Typically, based on my research, you would simply spin up the Home Assistant Binding inside openHAB to automatically ingest and parse the MQTT discovery topics broadcasted by Zigbee2MQTT. That would magically populate your Things with their Channels. Unfortunately, under openHAB 5.1, this binding relies on internal native components via GraalVM that crash out on non-mainstream operating systems like OpenBSD.
I eventually reached out and asked about the issue on the openHAB Community Forums. The experience was awesome—the community was incredibly quick to respond with highly helpful insight, pointing me directly to the core tracking issue on GitHub (#19276).
The Workaround: A Custom Discovery Bridge
Since I didn’t want to map dozens of channels manually in the openHAB UI, I wrote a lightweight python helper script that runs as a system daemon. It listens directly to the Mosquitto MQTT broker configuration topics and dynamically writes out matching .things files into /etc/openhab/things/.
It is still explicitly fitted to the properties of my specific collection of switches and sensors, but it saves hours of tedious UI assembly. You can grab or fork the script here: github.com/buzzdeee/mqtt_discovery_bridge.
Inhabiting openHAB: Extensions and Populated Things
With the bridge script automatically managing file-based generation on the backend, we still need to make sure openHAB has the core translation tools installed to understand the incoming data stream.
Heading into the openHAB Add-on Store, I installed the foundational MQTT Binding alongside a few essential data transformations required to handle the payload varieties: Jinja, JSONPath, and Regex.

The required MQTT binding and transformation add-ons inside openHAB
Once those add-ons were active and the Python daemon started spitting out files into the configuration directory, openHAB automatically picked them up. Navigating over to Settings -> Things reveals the payoff: the entire roster of Zigbee devices is populated, initialized, and online without clicking through a single wizard loop.

The automatically populated list of Zigbee hardware Things
Opening up a specific entity—in this case, the microwave-radar-based SONOFF presence sensor—shows how cleanly the properties are mapped out. The internal state exposes its available operational data pipelines, giving me instant access to properties like presence detection, illumination metrics, and motion status channels ready to link to UI items or automation scripts.

Inspecting the generated data channels of the SONOFF Presence Sensor
Linking Items, Building Pages, and Going Mobile
With the data channels exposed, the final step to make this fully interactive is creating human-readable Items and pinning them to a control dashboard.
I created two items, and linked respective channels of these things to it.

Linking the raw hardware channels to functional openHAB Items
Once those Items were configured and worked, I now had to add them to a dashboard layout. Using the built-in layout manager, I dropped both controls onto a dedicated landing page for some quick testing.

OpenHAB Landing page with two items and controls
The absolute best part of setting it up this way? It transfers perfectly over to the native mobile app ecosystem. Opening up the openHAB mobile application syncs the new page instantly over the local network layer.
As you can see, the page renders beautifully on mobile, giving me a snappy, real-time interface to trigger one of the light bulbs in my room.

The finalized UI layout running natively on the openHAB mobile application
Swiss-Army Debugging Snippets
While assembling the custom discovery bridge and monitoring raw JSON parameters passing back and forth across Mosquitto, besides reading through log files, these commands below were incredibly helpful:
Sniffing Live MQTT Traffic
# Watch targeted setting adjustments
mosquitto_sub -h 127.0.0.1 -v -t "zigbee2mqtt/Spot am 3D Drucker/set"
# Intercept all status broadcasts for a specific device
mosquitto_sub -h 127.0.0.1 -v -t "zigbee2mqtt/Spot am 3D Drucker/+"Forcing Test Payload Broadcasts
mosquitto_pub -h 127.0.0.1 -t "zigbee2mqtt/Spot am 3D Drucker/set" -m '{"effect": "blink"}'Managing Stuck Items via the openHAB Karaf Console
# Connect to console environment
ssh openhab@localhost -p 8101
# Inspect active things
openhab:things list
# Wipe a faulty discovery entity manually
openhab:things remove sony:scalar:fcf1523cd795
# Find and uninstall a rogue binding bundle
openhab> bundle:list | grep -i sony
# 369 | Active | 80 | 4.3.0.202412181559 | openHAB Add-ons :: Bundles :: Sony Binding
openhab> bundle:uninstall 369
# Clear out a corrupted automatic inbox queue
openhab:inbox list
openhab:inbox clearWhat’s Next?
With the system running stably on the fanless hardware, I can easily cruise along with my bridge script until openHAB 5.3 lands with the structural upstream fixes.
Now that the foundational data lanes are rock solid, what should I tackle next? I’m still figuring out the basic building blocks, so there’s a lot left to explore:
- Creating and linking up items to channels: Diving deeper into item types (Dimmer, Color, Number) and mastering state transformations so raw payloads display cleanly.
- Grouping items: Leveraging openHAB’s group item aggregation features to toggle entire rooms at once or compute average temperatures dynamically.
- Building a meaningful dashboard: Moving past basic test grids to design intuitive, high-WAF (Web Application Frontend) semantic interfaces for daily mobile and desktop use.
- Modeling the Semantic Home: Mapping everything out using openHAB’s Semantic Model (Locations, Equipment, Points) so the system understands exactly where a sensor physically sits in the house.
- Writing foundational rule scripts: Setting up your first event-driven automations, like utilizing the SONOFF presence sensor to auto-trigger the spot lamps when someone approaches the desk.
- Persistence & Data Logging: Setting up rrd4j or InfluxDB storage backends to start tracking temperature and humidity patterns over time without bloating the thin client’s flash memory.