| OPENHAPD(8) | System Manager's Manual | OPENHAPD(8) |
NAME
openhapd — HomeKit
Accessory Protocol daemon
SYNOPSIS
openhapd |
[-fnv] [-c
file] |
DESCRIPTION
The openhapd daemon uses the HomeKit
Accessory Protocol (HAP) so that Apple HomeKit can control MQTT-connected
Tasmota devices on OpenBSD.
openhapd is a bridge between HomeKit
controllers, for example, the iOS Home app, and devices that MQTT controls.
It changes HAP requests into MQTT commands. It sends changes of the device
state back to HomeKit.
Pairing and transport security obey the HAP specification:
- Pair-setup with SRP-6a (Secure Remote Password)
- Pair-verify with Curve25519 (X25519) key exchange
- Ed25519 signatures for accessory and controller identity
- HKDF-SHA-512 key derivation
- Session encryption with ChaCha20-Poly1305 AEAD
- Service advertisement over mDNS
openhapd advertises the service over mDNS.
To do this, it connects directly to the control socket of
mdnsd(8),
/var/run/mdnsd.sock.
openhapd keeps the socket open for its full lifetime
and spawns no helper process. When openhapd stops,
it closes the socket, and this removes the advertisement.
mdnsd(8)
must run when openhapd starts. If it is not, HomeKit
controllers cannot find the accessory. In that condition, the daemon starts,
writes a warning to the log, and serves the paired controllers. Discovery is
not available until you restart openhapd while
mdnsd(8) runs.
On OpenBSD, the daemon drops privileges and advertises itself. Then it restricts its own syscall surface with pledge(2) to “stdio rpath wpath cpath fattr flock inet dns unix” before it receives any network input. The “proc”, “exec” and “prot_exec” promises are absent by design. These restrictions are in effect only on OpenBSD. On other systems, the daemon runs without restrictions.
The options are:
-cfile- Read the configuration from file. The default is /etc/openhapd.conf.
-f- Run in the foreground. By default,
openhapddaemonizes and logs to syslog(3). With this option,openhapdstays in the foreground and logs to stderr. -n- Do a check of the configuration file for syntax errors and exit. No daemon starts.
-v- Increase the log verbosity. You can give this option more than one time.
Each use increases the log level:
-vgives the debug messages, and-vvadds trace messages.
FILES
- /etc/openhapd.conf
- The default configuration file.
- /var/db/openhapd/
- The state directory that holds the pairing database and the cryptographic keys.
- /var/db/openhapd/pairings.db
- The database of paired HomeKit controllers, mode 0600.
- /var/db/openhapd/accessory_ltsk
- The long-term secret key (Ed25519 private key), mode 0600.
- /var/db/openhapd/accessory_ltpk
- The long-term public key (Ed25519 public key), mode 0644.
- /var/db/openhapd/state.json
- The configuration number, the configuration digest, and the counter of failed pairing attempts, mode 0600. An older installation kept each of these in a file of its own. The daemon reads those files one time and folds them into this one.
- /var/run/openhapd/control.sock
- The control socket, mode 0600 in a directory of mode 0700.
hapctl(8) asks the running daemon
for its status over it. The daemon creates the directory while it is still
root, because OpenBSD clears
/var/run at boot, and it removes the socket at a
clean shutdown. The
controldirective of openhapd.conf(5) moves the socket or turns it off. - /var/run/openhapd.pid
- The process ID file. The daemon takes it while it is still root, and holds
the lock for its whole life. The file stays root-owned, so the
unprivileged daemon cannot rewrite it.
openhapddoes not remove the file at exit: an unlink(2) in root-owned /var/run needs a directory permission that the daemon gives up at the privilege drop. A leftover file names a process that no longer runs, and hapctl(8) reports that as “not running”.
On OpenBSD, the daemon
restricts its filesystem view with
unveil(2). It can
read nothing
outside of the list that follows. If openhapd cannot
see a file, examine this list first. If an optional path is absent, the
daemon starts. If a necessary path is absent, this is a fatal configuration
error.
- db_path
- (necessary, read-write-create) the state directory above.
- /dev/urandom
- (necessary, read) randomness for all key material.
- perl library directories
- (necessary, read) the library tree of the interpreter, for the one module that the daemon loads late, on the MQTT reconnect path.
- configuration file
- (optional, read) the file that the
-coption names. - /var/log/openhapd.log
- (optional, write) the daemon-mode log; the daemon never creates this file
in
-fmode. - /var/run/mdnsd.sock
- (optional, read-write) after a pairing change, the daemon makes a new connection to mdnsd(8) to advertise again.
- control socket directory
- (optional, read-write-create) the directory of the control socket, not the
socket. bind(2)
and unlink(2)
both change a directory entry. The row is absent when
control offturns the socket off. - /etc/resolv.conf, /etc/hosts, /etc/services, /etc/protocols
- (optional, read) the resolver files; the daemon uses them when
mqtt_hostis a name and not an address. - /etc/localtime
- (optional, read) timestamps.
SIGNALS
SIGTERM,SIGINT- Shut down. The signal sets a flag. The event loop sees the flag at the end
of the current pass, and the daemon then withdraws its mDNS advertisement
and closes the listener and every client connection. The daemon exits 0.
Nothing exits from inside the signal handler, thus a connection is never dropped in the middle of a response.
SIGHUP- Reload. The daemon opens its log again and keeps serving. Use this after
newsyslog(8)
rotates /var/log/openhapd.log.
rcctl reload openhapdsends this signal. In releases before 2026 the signal ended the daemon, which is the opposite of a reload.
The daemon does not remove /var/run/openhapd.pid at exit. See FILES.
EXAMPLES
Do a check of the configuration file:
# openhapd -n
Run in the foreground with verbose logging:
# openhapd -f -v
Start the daemon the usual way, through rc(8):
# rcctl enable openhapd # rcctl start openhapd
DIAGNOSTICS
openhapd logs to
syslog(3) with the
“daemon” facility. When the daemon runs in the foreground with
-f, it writes the messages to
stderr.
These are some of the log messages:
- “Starting OpenHAP server”
- The daemon started correctly and now listens for connections.
- “Not paired - use Home app with PIN: XXX-XX-XXX”
- The accessory is not paired with a HomeKit controller. Use the PIN from the message to pair through the iOS Home app.
- “Connected to MQTT broker at host:port”
- The daemon connected to the MQTT broker.
- “MQTT broker not available, will retry in background”
- The daemon did not connect to the MQTT broker at startup.
openhapdcontinues to try to connect in the background. - “Paired with controller: identifier”
- The pairing with a new HomeKit controller was successful.
- “Secure session established”
- The daemon started an encrypted session with a paired controller.
A configuration file that does not parse is a fatal error.
openhapd writes the file, the line number, and the
reason to stderr, and it exits 1 before it opens the
log:
/etc/openhapd.conf:14: cannot parse: hap_prt 51827
The daemon does not fall back to the defaults, because the
operator did not choose them. Use -n to check a file
before you restart the daemon.
SEE ALSO
syslog(3), openhapd.conf(5), hapctl(8), mdnsd(8), rc(8), rc.conf(8)
HomeKit Accessory Protocol Specification (Non-Commercial Version): https://developer.apple.com/homekit/
STANDARDS
openhapd obeys the HomeKit Accessory
Protocol Specification R2 for IP accessories. These documents specify the
cryptography that it uses:
- RFC 5054: Using the Secure Remote Password (SRP) Protocol for TLS Authentication
- RFC 7539: ChaCha20 and Poly1305 for IETF Protocols
- RFC 5869: HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
- RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA)
- RFC 7748: Elliptic Curves for Security (Curve25519)
HISTORY
The openhapd daemon was first available in
2025.
AUTHORS
Dick Olsson <hi@senzilla.io>
CAVEATS
For correct operation, openhapd needs an
MQTT broker, for example,
mosquitto(8),
and mdnsd(8). Both
must run. If one of them is not available, the daemon starts and does what
it can. The daemon tries the MQTT connection again in the background. The
mDNS discovery is not available until you restart the daemon.
At the first startup, if the accessory is not paired, the daemon writes the HAP PIN code to the log. Thus, limit the access to the system logs.
The pairing data in /var/db/openhapd/ holds cryptographic keys. The owner of the directory must be “_openhap”, and the mode must be 0700.
BUGS
openhapd operates only with Tasmota
devices over MQTT. It does not implement HAP over Bluetooth Low Energy.
SECURITY CONSIDERATIONS
openhapd starts as root to set the correct
ownership of /var/db/openhapd. Then it drops to the
“_openhap” user and publishes its mDNS advertisement. Then it
restricts itself: first the filesystem view with
unveil(2), and
then the syscall surface with
pledge(2). The
daemon accepts network connections only after these steps.
Because the restrictions come after the privilege drop, they apply to all code that receives untrusted input. They do not, and cannot, apply to the short root phase. But that phase receives no untrusted input.
The promise set is “stdio rpath wpath cpath fattr flock inet dns unix”:
stdio- always necessary.
rpath- /dev/urandom, state reads, and the MQTT module that the daemon loads late.
wpath,cpath- to write, create, and remove the state files and the daemon log.
fattr- the daemon sets the mode of the key material to 0600.
flock- locks protect the state files.
inet- the HAP listener and the MQTT connections.
dns- to resolve
mqtt_hostwhen it is a name. unix- after a pairing change, the daemon makes a new connection to mdnsd(8) to advertise again.
The proc, exec and
prot_exec promises are absent by design: the daemon
spawns no processes and loads no shared objects after startup. The section
FILES lists the unveiled paths and the
access type of each path.
The mdnsd(8) control socket has owner root:wheel and mode 0660. Thus, the “_openhap” user must have read-write access to the socket. In the standard setup, the “_openhap” user is a member of the wheel group, and this gives the access.
The restrictions are in effect only on OpenBSD. Linux and Darwin are development platforms, and there the two mechanisms do nothing. On these platforms, the daemon runs without restrictions.
A pledge violation stops the daemon immediately with
SIGABRT. The kernel then writes a line to
dmesg(8) and
/var/log/messages. This line names the process and
the syscall that is not permitted. If openhapd stops
and you do not know why, examine these logs first.
A path outside of the unveiled view does not stop the daemon. The
operation on that path fails with ENOENT or
EACCES. Usually, the daemon log then shows a
warning.
| July 29, 2026 | OpenBSD |