Zkitszo - The Real
September 19, 2026

Wipe It, Rebuild It- A Home Nzyme Fleet on 5 Raspberry Pis

There are five little computers scattered around the house listening to every WiFi and Bluetooth signal that drifts by.. four of them report back to the fifth, which keeps the records. One morning I realized I could no longer tell you which one was which, one of them had quit capturing altogether, the web page kept throwing me back to the login screen, I'd talked myself into believing the whole install was "corrupt"... you know how it goes once that idea gets in your head.

It wasn't corrupt. I wiped it anyway- saved the data first, then cleared everything off to start fresh. Mostly this post is for me to remember how it all goes back together, since I will forget. If you run Nzyme at home, or you've been thinking about it, maybe it saves you the morning it cost me.

Nzyme, the Node and the Taps

Nzyme describes itself as "Open Source WiFi, Bluetooth, and Ethernet Threat Detection". I'd put a small asterisk on the "open source" part since the licence is the SSPL, with a company behind the project that sells its own sensor hardware- nothing wrong with that, the project has been around since 2017, I'm just telling you what you're getting into. Version 2.0 is still in alpha. I was running 2.0.0-alpha.19 on Raspberry Pi OS 13.

The way it's laid out- there's a node, the brains of the operation, a Java program with a web page out front and a PostgreSQL database behind it. Then you have taps, small capture programs that you place around the house, each one with a USB WiFi radio sitting in monitor mode along with the Pi's own built in Bluetooth. A tap doesn't send the node everything it hears, only a summary, so even a tiny Pi Zero manages to keep up.

One Node, Five TapsFive taps- tap-000 runs on the node itself. Taps report every few seconds, the node keeps the records.node-000 (Raspberry Pi 5)Nzyme node: web interface + REST APIPostgreSQL 17 databasealso runs its own tap, tap-000nightly dump at 03:00tap-001Pi 5RT5572 radioWiFi + Bluetoothtap-002Pi 5MT7612U radioWiFi + Bluetoothtap-003Pi 5RT5572 radioWiFi + Bluetoothtap-004Pi Zero 2 Wno radio yetBluetooth onlyreports over HTTPS, each tap signed in with its own 64-character secretGreen = capturing WiFi and Bluetooth. Amber = running, but one capture type missing.

Five taps across five Pis, since the node runs a tap of its own too. That's four Raspberry Pi 5s and one Pi Zero 2 W.. funny enough I had that backwards in my head, I would have told you with a straight face that I owned a single Pi 5. Turns out I own four of them!

Somebody is going to ask about what this thing collects, so- monitor mode only listens. Nothing gets transmitted, nobody's traffic gets read, what it picks up are the names and addresses that devices shout out all on their own. That said, the rules around even that differ depending on where you live, some places treat a MAC address as personal data, so have a look at what applies to you before you set one of these up.

Which Pi Is Which?

None of them were labelled. Of course they weren't.

What I ended up doing was pulling the WiFi adapter out of one Pi, then checking the node's list of taps to see which tap had just lost its radio.. sticker on that Pi, adapter back in, on to the next one. A bit silly, I know- it does work though, with the bonus that every adapter gets reseated along the way, which never hurts with USB. The one that fooled me was the node itself, since it carries two adapters.. I pulled one of them, nothing complained, so I sat there waiting for a while.

If your adapters are fighting each other before you even get this far, I went through that whole song and dance once already in 1 Pi, 2 WiFi.

The Pandas

same brand, not the same radio... not the same price either

All of my capture radios are made by Panda Wireless, a brand Nzyme lists among its tested adapters. They aren't all the same model though. The PAU09 has a Ralink RT5572 inside it- dual band, 802.11n, USB 2. The PAU0D has a MediaTek MT7612U, which makes it an 802.11ac radio. When I bought mine about five months ago there was a $40 difference between the two.. for a little USB dongle that is a big jump.

Which one is "better"? For this job I'd say the PAU0D. The way I understand it, an 802.11n radio still hears the beacons and probe requests coming off an 802.11ac network, since those get sent at the old slow rates.. so it isn't blind. What it can't do is follow the actual 802.11ac data frames or the wide 80 MHz channels, where the ac radio can. I don't have a tidy source to point you at for that, so if I've got it wrong please tell me. Whether that's worth another $40 per tap is up to you- for one or two taps I think it is, for all five.. hey, they add up!

A catch with the PAU0D- mine show up on the Pi 5's USB 3 bus. Intel put out a white paper back in 2012 about USB 3.0 signalling spraying noise into the 2.4 GHz band, which happens to be where half of your WiFi capture lives. That's Intel's finding, not a Raspberry Pi recommendation- still, if a USB 3 adapter seems a bit deaf on 2.4 GHz, try it in one of the USB 2 ports before you blame the adapter.

Power is the other thing to keep in mind. A Pi 5 shares 600 mA across all four of its USB ports unless it detects a proper 5 V 5 A supply.. so two radios hanging off one Pi can brown out in ways that look like a bad adapter. There's an override, usb_max_current_enable=1 in config.txt, which only helps if your power supply can really deliver what you're telling the Pi it can.

Save Everything First

The old database was holding 600,186 access point records, 351,584 network names, 111,271 client devices and 349,485 Bluetooth devices.. months of the neighbourhood drifting past the house. I wasn't about to lose that to a reinstall.

Nzyme's documentation has nothing on backups, at least no page that I could find. Underneath it's an ordinary PostgreSQL database though, so the ordinary PostgreSQL tools do the job. I ran these from my main computer so the backup landed somewhere other than the Pi it came from:

ssh node 'sudo -u postgres pg_dump -Fc -Z6 nzyme' > nzyme-full.dump
ssh node 'sudo -u postgres pg_dumpall --globals-only' > pg-globals.sql
chmod 600 nzyme-full.dump pg-globals.sql

The second file contains password hashes, hence the chmod. For what it's worth the database reported 2.5 GB on disk while the dump came out at roughly 90 MB.. my guess is that most of the difference is indexes and dead space that a dump simply doesn't carry along, rather than some miracle of compression.

...then make sure it actually restores

A backup file sitting on a drive tells you nothing until you've loaded it back in somewhere. I restored mine into a scratch database on the node (mine had the room to spare- check that yours does), counted the rows in the big tables on both the scratch copy and the live one, then threw the scratch copy away once the numbers matched. The first and last lines get run on the node, the middle one gets run from wherever the dump file is sitting:

sudo -u postgres createdb nzyme_verify
cat nzyme-full.dump | ssh node 'sudo -u postgres pg_restore --exit-on-error --no-owner -d nzyme_verify'
sudo -u postgres dropdb nzyme_verify

Once I was happy with that, I stopped everything- the taps first, the node last- then took one more dump while nothing was writing to the database. That final one matched the live database row for row. While you're at it, grab a copy of /etc/nzyme from every Pi. One more thing.. put the archive somewhere your own tidy-up jobs won't reach, since my regular backup folder deletes anything older than 14 days, which would have quietly eaten the whole point of the exercise.

Why Not Just Restore It Then?

Fair question. It was the same Nzyme version, so loading the old database straight into the new install would have worked fine. I didn't want that. What I wanted was a complete fresh start- a clean install capturing all on its own, without any of the extras I'd piled on top over the months, so that the next time something breaks I have a picture of what "normal" looks like. The old data stays parked in case it's ever required.. if I do load it one day it goes into a separate database off to the side, never over top of the live one.

Wipe It, Put It Back

I did not reflash any SD cards. Three of my Pis were on WiFi only at the time, so a botched operating system reinstall would have been a great way to lock myself out of machines I can't easily get at. Only the application got wiped.

Before purging anything, download the new packages then verify their signatures, that way you know you can put it all back. Nzyme explains the process at go.nzyme.org/release-verification, with the key's SHA-256 printed in the README.. check the key against that first, since a key downloaded from the same place as the package doesn't prove much all by itself. The signatures are Ed25519, which the usual openssl dgst can't handle, so it's pkeyutl with -rawin:

sha256sum nzyme-alpha.pub     # compare with the value in Nzyme's README
openssl pkeyutl -verify -pubin -inkey nzyme-alpha.pub.pem \
  -rawin -in nzyme-node_rpios-13trixie-arm64.deb \
  -sigfile nzyme-node_rpios-13trixie-arm64.deb.sig

Now for the part with no undo. A warning before you run it- on the node, the node software shares the /etc/nzyme folder with the node's own tap. Removing that folder is exactly what you want at this step.. run the same cleanup again later thinking you're only tidying up "just the tap" though, there goes your freshly made node config along with it. That very nearly happened on mine.

sudo apt-get purge -y nzyme nzyme-tap
sudo -u postgres psql -c "DROP DATABASE nzyme" -c "DROP ROLE nzyme"
sudo rm -rf /usr/share/nzyme /var/log/nzyme /etc/nzyme

With the old one gone, install the node package, then give it a new empty database with a user of its own:

sudo dpkg -i nzyme-node_rpios-13trixie-arm64.deb
sudo -u postgres psql -c "CREATE DATABASE nzyme" \
  -c "CREATE USER nzyme WITH ENCRYPTED PASSWORD 'A-LONG-RANDOM-ONE'" \
  -c "GRANT ALL PRIVILEGES ON DATABASE nzyme TO nzyme"
sudo -u postgres psql -d nzyme -c "GRANT CREATE ON SCHEMA public TO nzyme"

Whatever password you picked goes into the database_path line of /etc/nzyme/nzyme.conf. Give the node a name in there while you have the file open. The two addresses need changing as well- they sit inside the interfaces block, not at the top level of the file:

interfaces: {
  rest_listen_uri: "https://0.0.0.0:22900/"
  http_external_uri: "https://YOUR-NODE-ADDRESS:22900/"
}

Fresh out of the box the node only listens on localhost- safe, sure, also useless, since none of the taps can reach it. Setting it to 0.0.0.0 opens it on every network interface the Pi has, which I'm only comfortable with behind my own network/ firewall. Mine can be reached from inside the house and over my VPN, nowhere else.

After that it's sudo nzyme --migrate-database, enable the service, then wait.. mine took about 35 seconds before the web page answered. On the first visit the browser complains loudly about the self-signed certificate. I clicked through that, then created the first admin account.

"It Keeps Kicking Me Out"

Log in, dashboard, click something, login page, log in, dashboard, login page, log in, log in, log in, check the password, log in, blame MFA, log in.....

(I had turned MFA off. Nzyme switches it on for every account by default, with the docs telling you plainly not to do what I did. On a box that only I can reach, I did it anyway.)

When I went looking, the node's log showed 14 logins that morning, five of them within the same 30 seconds.

What's actually going on- Nzyme keeps a single session per user. Each time you log in successfully it throws away whatever session that same user already had. You can see it right in the source code, on the line just before the new session gets created:

nzyme.getAuthenticationService().deleteAllSessionsOfUser(user.get().uuid());

Picture what that does in practice.. two tabs/ windows open on the dashboard, each one logging the other one out. A tab up against a password manager that helpfully fills in then submits the login page all by itself. A person in a browser up against a script that happens to use the same account. Every one of those turns into an endless loop of getting kicked out. I couldn't find this mentioned anywhere in the documentation- the page that ought to cover session settings says "This guide is not published yet."- nor could I find anybody who had opened an issue about it.

What fixed it for me was going back to the way the old install had been set up, which I'd completely forgotten the reason for.. one super admin account for me, a second one for anything automated, so a script logging in can't boot me out of the browser.

Taps, One at a Time

With a brand new database none of the old taps exist anymore, so each one has to be added again on the node before the tap software on the Pi is allowed to talk to it. In the web interface you add the tap, give it a name, then open it up.. on its details page there's a long 64-character secret. That secret gets pasted into /etc/nzyme/nzyme-tap.conf on the Pi as the leader_secret.

My advice is to begin with the config file that ships with the package rather than dragging your old one along. What I'm showing below are only the lines I changed.. leave everything else in that file alone, there are required settings further down that the tap won't start without:

[general]
leader_secret = "PASTE-THE-64-CHARACTERS"
leader_uri = "https://YOUR-NODE-ADDRESS:22900/"
accept_insecure_certs = true

[bluetooth_interfaces.hci0]
active = true        # was false, other lines in this block untouched

[wifi_interfaces.wlxYOURADAPTER]
active = true
channels_2g = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
channels_5g = [36, 40, 44, 48, 149, 153, 157, 161, 165]
channels_6g = []

The accept_insecure_certs line is in there since the node's certificate is self-signed.. this is a tap talking to a node across my own network, so I'm fine with it. NetworkManager also has to be told to keep its hands off the capture adapter, otherwise it keeps trying to manage a radio you want left alone. That goes in /etc/NetworkManager/conf.d/99-nzyme-unmanaged.conf:

[keyfile]
unmanaged-devices=interface-name:wlx*

Run sudo systemctl reload NetworkManager afterwards. Keep in mind that wlx* only matches adapters that are named after their MAC address.. if yours shows up as wlan1, put that name in there instead.

I brought the taps up one at a time, waiting until the node showed each one reporting in before moving on to the next, so that if something went sideways I'd know exactly which Pi to go look at. All five got created through the node's REST API rather than the web page, mostly to stay clear of that login mess.. the API isn't documented anywhere, so I'm not going to pretend that part of this is a tutorial.

A few minutes after the last tap came up there were 39 access points and 46 client devices in a database that had been completely empty ten minutes earlier.

Country Codes, Channels

something that is easily missed... I missed it

Something that is easily missed, which I did- it drove me nuts for the few minutes I'd gone in circles trying to figure out why my setup wasn't picking anything up- you have to set the country, the country code. Different regions of the world have different configurations... different channels.. different frequency ranges... nothing substantial, but it can be... in this case it had completely stopped the adapter from capturing any signals- so, I guess, substantial. Keep that on your mind if your setup isn't capturing anything.

Take channels 12 and 13 for example- these 2 channels have legal consequences in some countries. I think Japan has the full meal deal of channel selection- Canada, ain't that bad, I'd say Canada is up there for channel count. (I looked it up afterwards in the regulatory database that Linux uses.. the United States stops at channel 11, Canada goes to 13, Japan goes to 13 then adds channel 14 on top for the old 802.11b gear only.)

Each channel represents a frequency range... different countries "lease" or sell these ranges to private entities... companies... military... a cellphone company.... satellite services... they pay BIG bucks for these... they don't mess around. It doesn't come to mind, but technically, surfing the net... you receive data, but you also send it back- no thought goes into it- you flip open the laptop, in your browser your fingers dramatically type in G O O G L E a dot.... followed by C O M.. followed by a very enthusiastic tap on "enter" or a very brisk click on a magnifying glass icon.. these days, almost faster than taking that action, a web page loads... that action actually sent data.. a request to load that data.. you transmitted data... a transmission,,, on what frequency? Exactly, we don't think twice about it...

...but transmit on the amateur bands..... you require your amateur radio licence... transmit on frequencies that a cellphone company paid for.... you may get a hefty bill..... transmit on aviation or emergency frequencies.... it may lead to a big fine, your gear taken away, confiscated... even handcuffs and jail time. Here in Canada the Radiocommunication Act puts it at up to $5,000, up to a year, or both, for an individual.. with the radio gear forfeited on top.

So mind those channels, the frequencies you play around on, the country codes / countries that you've set your adapter or other WiFi device to. A tap in monitor mode is only listening, which is a different thing from transmitting- it's the transmitting that lands people in trouble. That being said... since you can set a country code... with a specific need to do so.. selecting a specific country may assist in your endeavours.

Checking / changing it on the Pi:

iw reg get                                        # what the Pi thinks the country is right now
sudo raspi-config nonint do_wifi_country CA       # set it for good (CA is mine, use yours)
sudo iw reg set CA                                # or set it only until the next reboot
iw phy phy0 channels                              # the channels your radio is now allowed to use

Anything in that last list marked as disabled is off limits for the country you've set. The channels you want the tap to hop across then go into the channels_2g / channels_5g lines of nzyme-tap.conf, the ones shown further up.. those happen to be mine, for Canada, so don't copy them blindly.

Where's the Bluetooth?

Every tap was reporting in, WiFi was pouring into the database.. Bluetooth sat at a big fat zero, across all five taps. Running bluetoothctl devices on the Pis showed seven or eight devices each, so the taps were hearing Bluetooth just fine.... the node simply wasn't keeping any of it.

This one is in the documentation, I just hadn't read that page. Bluetooth is marked "Experimental". It has been switched off by default ever since alpha.15. To make it worse it isn't a single switch, it's three of them, as the docs put it:

Enabling a subsystem will not automatically enable it for all underlying organizations and tenants.

You turn it on at the system level first, then for the organization, then again for the tenant. With all three switched on it took less than a minute before every one of the Pi 5 taps had eight or nine Bluetooth devices logged.

The thing that pointed me in the right direction was the old backup. Nzyme stores its settings in a database table called registry.. you can pull that one table out of a dump file as plain text without restoring anything:

pg_restore -a -t registry -f - nzyme-full.dump

I did that with the old dump, ran the same query against the new database, then put the two outputs side by side. The old one had the Bluetooth subsystem set to true at all three levels, whereas the new one didn't have those lines at all.. from there it was obvious what I'd forgotten. A word of caution- that table also holds keys and secrets, so don't go pasting it into a forum post. The drone Remote ID ("UAV") switch turned out to be off on my fresh install too.. I can't find that one documented either way, so treat it as something I saw rather than something I know. The old install also had Nzyme Connect switched on for vendor lookups, which wants its API key entered all over again on a new database.

Pull the Adapter, See What Happens

Which brings me back around to the tap that started this whole thing. Its USB radio had gone missing.. rather than carrying on with Bluetooth, which was working perfectly well, the entire tap was dead. Is that really how it's meant to behave?

I tried it out on one of the healthy Pi 5 taps to find out. You don't even need to walk over to unplug anything- Linux will detach a USB device for you if you hand it that device's bus id. It has to be the id of the whole device (something like 3-1), not the longer 3-1:1.0 kind that you'll see in most examples:

readlink -f /sys/class/net/wlxYOURADAPTER/device    # ends in something like .../3-1/3-1:1.0
echo 3-1 | sudo tee /sys/bus/usb/drivers/usb/unbind
echo 3-1 | sudo tee /sys/bus/usb/drivers/usb/bind    # puts it back
Same Missing Adapter, Two Very Different DaysTested on one Pi 5 tap, nzyme-tap 2.0.0-alpha.19, by detaching the USB radio in software.Adapter vanishes while the tap is runningtap processstays upBluetooth scanningcontinuesreports to the nodecontinuelogabout 900 error lines a secondthe log linelibpcap error: The interface disappearedTap starts with the adapter missingtap processexits, status 71systemdrestarts it, 5 times in 45 sBluetooth scanningstopsreports to the nodestopthe log lineCould not initialize ChannelHopperSo a reboot after an adapter fault turns a limping tap into a dead one. Reported upstream as Nzyme issue #1520.

When the adapter disappears while the tap is already running, the tap survives. Bluetooth carries on, reports keep arriving at the node. The WiFi capture part of it loses its mind though, retrying as fast as it possibly can while writing libpcap error: The interface disappeared to the log somewhere around 900 times a second. The system log threw away roughly half of those as suppressed.. the other half, about 35,000 lines in 80 seconds, still got written. To an SD card!

When the tap starts up with the adapter already missing, it's a different story altogether- the tap just quits. Exit status 71, the old BSD code for "system error". Ten seconds later systemd starts it again, it quits again, around it goes.. I counted five starts in the first 45 seconds, it was still going when I stopped watching. Bluetooth and the reporting both go down with it, even though neither one has anything wrong with it.

So picture a tap that has lost its radio but is limping along fine on Bluetooth.. then the power blips, the Pi reboots, that tap never comes back.

There's already an Nzyme issue covering exactly this, #1403- "A tap with a missing interface should still spin and report". It was closed back in March with Sona WiFi, Bluetooth, Ethernet and GNSS all ticked off.... "WiFi Classic" was left unticked. Somebody else ran into the very same exit 71 in January because of a placeholder adapter name in their config (#1379).. it took their Bluetooth down too. I filed a follow-up as #1520.

An odd detail I tripped over while reading around- in a 2024 thread the author says version 2.0 is "using Netlink directly, without using libpcap at all". My error message says otherwise. Maybe Netlink handles the channel changes while libpcap still carries the frames.. I honestly don't know, the docs don't say.

Until that gets fixed, the only adapters listed in my tap configs are ones that are physically plugged in.

Other Considerations

The Pi Zero 2 W has two micro-USB ports, only one of which carries data. A USB Ethernet adapter and a capture radio means a hub.. mine is still waiting on one. The replacement radio for it is a Panda with one of its antenna connectors snapped off- no idea yet how deaf that makes it, I'll report back.

The Bluetooth adapter on a tap wants nothing paired to it. If you ever paired a keyboard to that Pi, go unpair it.

alpha.18 and alpha.19 both have packages and git tags, yet no release notes. The last written changelog is for alpha.17, from April 2025.

Floor Plans

I go off topic here a little- skip to the next heading if you only came for the rebuild.

Nzyme can place a device on a drawing of your house by comparing how strongly each tap hears it, as long as three or more taps on the same floor can pick it up.. a fourth helps a lot. For that it wants a JPG or a PNG of the floor. I don't have drawings of my house, nor was I about to learn CAD for the occasion.. so there's now a little floor plan editor. It's a single HTML file that opens in a browser with nothing to install. You add a room, drag it where it belongs, resize it, type in the width and length in feet.. there's a tab each for the main floor and the basement, with an export button that spits out a PNG carrying a 10 ft scale bar. It remembers your layout in the browser between visits. That's the whole thing. It's up on GitHub if you want it-

https://github.com/MissSophieSterling/nzyme-floorplan-editor

The fresh database has forgotten where all my taps sit, so the floor plan goes back in next.

-OK, back on topic.

ESP32 Alert Panels

Also started- a Cheap Yellow Display (the ESP32 board with the 2.8" touch screen) as a little fleet health panel. Every 15 seconds it asks a small bridge service running on the node how the taps are doing, shows green/ amber/ red, lists any alerts, even lets you clear one right from the screen. The ESP32 itself never gets handed the Nzyme admin password.. that stays on the bridge. There's a pixel art mascot on it that reacts to the state of things- I sent the first one back for not being mean enough.

*more to come- that one gets its own post when it's done.

What's Next

The hub for the Zero. The floor plan. Then there are the Samsung 990s- I'd gotten two of the 2 TB sticks, one for the node so that PostgreSQL can finally get off the SD card, the other for the Mac mini inside an external adapter that I haven't bought yet. The node still needs setting up for its one. Funny thing.. they've been appreciating in value ever since I bought them. An "investment", apparently.... the only one I've got that has gone up.

Alright, that's the fleet listening again.

Oh- if you paste a key or a token into a chat window, or leave a screenshot of one lying around on your desktop while you're fighting with all of this.. revoke it, then make a new one. Takes a minute.

Double oh... could you build your own Nzyme? Probably, the listening part of it anyway. I'd rather keep building on top of this one.

Running Nzyme at home? Which adapters are you using.. did your tap survive losing one? Leave a comment- if you've got a cleaner way of keeping a tap alive, I'd like to try it.

Glossary

  • Node — the Nzyme server: web page, REST API and the PostgreSQL database where everything is kept.
  • Tap — the small capture program on each Pi that listens to WiFi and Bluetooth and reports to the node.
  • Monitor mode — a WiFi radio mode that only listens, handing every frame it hears to the computer instead of joining a network.
  • Country code / regulatory domain — the setting that tells a WiFi radio which channels and power levels your country allows.
  • leader_secret — the 64-character key the node issues for one tap, so the node knows who is reporting.
  • Subsystem — Nzyme's name for a kind of data (WiFi, Ethernet, Bluetooth, UAV) that can be switched on or off at system, organization and tenant level.
  • SSPL — the Server Side Public License, the licence Nzyme uses; the source is available, but it is not an OSI-approved open source licence.
  • pg_dump / pg_restore — PostgreSQL's tools for writing a database to a file and loading it back.
  • Ed25519 — the signature type Nzyme uses on its packages.
  • 802.11n / 802.11ac — WiFi 4 and WiFi 5; an ac radio can follow faster, wider transmissions that an n radio cannot.
  • NVMe — a fast solid state drive on an M.2 stick; far kinder to a database than an SD card.
  • Exit status 71 — EX_OSERR, the traditional "system error" exit code.
  • Unbind — detaching a device from its Linux driver through sysfs, the software version of pulling the plug.
  • Remote ID — the identification broadcast drones are required to send, which Nzyme can pick up over WiFi.
  • Trilateration — working out where a transmitter is from how strongly several receivers hear it.
  • Cheap Yellow Display — the ESP32-2432S028, an ESP32 board with a 2.8 inch touch screen.

Sources

Comments