Linux static binaries compiled 2026-10-10. Copy, chmod +x, run. Zero dependencies.
user line in a root-owned path refuses to start instead of silently running as root, and the CLI needs root or membership of group apm. Run sudo apm healconf after installing: it checks your config and every include, explains each setting this version reads differently, and fixes it with a backup beside the file (--check only reports; --yes applies every safe fix). apm upgrade runs the check for you.
Wacatac.B!ml. This is a known false positive: its ML heuristic misfires on statically compiled Go binaries and UPX-compressed executables. It has been reported to Microsoft for review. All other major antivirus engines report these files as clean — see the VirusTotal links below for full scan results.
metadata.json (the SHA-256 of every file above), install.sh and each connector carry an ed25519 signature beside them as <file>.sig. The public key is compiled into every APM binary since v2.10: apm upgrade refuses anything that does not verify, and apm verify <file> <file>.sig checks a download by hand. Key fingerprint (SHA-256 of the public key): f6af819d1b56bcc075a009f5051bb3a4bcf5d0cd962b513d763898a8ca09527f
-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAJaQi2HxEjH2ncrimkjGwyyj1XYYwfqTtSPuNT2VIiy8= -----END PUBLIC KEY-----To check with OpenSSL:
openssl pkeyutl -verify -pubin -inkey apm.pub -rawin -in metadata.json -sigfile <(base64 -d metadata.json.sig)
A WebSocket client that vanishes — power cut, cable pulled, firmware wedged — sends nothing. dead_timeout (v2.6) catches that with kernel probes, but only on a listener APM terminates itself; behind nginx, a unix socket or Cloudflare the TCP peer is the proxy, which is alive, so the worker heard nothing until the keep_alive deadline, 120 s, or until the application's own heartbeat gave up. That chain is exactly what a device fleet behind a CDN runs.
ping_interval / ping_timeout / ping_miss — the fleet's dead-device detectorworker {
server ws://unix:./fleet.sock;
ping_interval 10000; # ping after 10 s of silence …
ping_timeout 2000; # … wait 2 s for the pong (and between retries) …
ping_miss 2; # … two unanswered pings and the client is gone: 14 s
}
After ping_interval of silence APM sends a ping and waits ping_timeout; ping_miss unanswered pings in a row and the client is closed with code 1001 (ping timeout) and the worker gets connectionClosed, exactly as for any dirty disconnect — no heartbeat code in the application. Any frame from the client is proof of life, so a talking connection is never pinged, and the per-connection ping goroutine is gone: the read deadline is the timer. Control frames are forwarded end to end by nginx and by Cloudflare, so the only requirement is a client that answers pings, which every browser and standard WebSocket library does. Unset, the timing is what every earlier version had (ping after keep_alive/2, closed at keep_alive), minus the pings a busy connection used to receive every 60 s regardless.
Measured on a real link cut: 7–9 s through Cloudflare → nginx → unix socket at ping_interval 5000, 3 s direct at 3000, 2 s with dead_timeout on top; on defaults still 103–119 s, and Cloudflare itself never reported a dead client. Also learned: Cloudflare closes a WebSocket with no frames at all after ~125 s and counts ping/pong as traffic.
session.setPing({ interval, timeout, miss }) (set_ping in Python, Perl and Lua) replaces the policy for one session, so a fleet can give some devices faster detection than others. Every ping now carries its send time and the pong echoes it, so each cycle measures the client's round trip; session.pingStats() returns the last sample and avg/min/max/count over the last 5 and 60 minutes, kept per connection in the daemon, and a built-in ws_rtt timing metric per worker shows the fleet in the GUI and on StatsD. All six connectors move to v3.4.0; the conformance harness covers both calls.
Upgrade notes: nothing changes unless you set ping_interval. Device fleets behind a proxy or CDN: set ping_interval 10000; ping_timeout 2000; ping_miss 2; on the worker and drop the application-level heartbeat if it was only there for liveness. Details in the timestamped notes beside this file.