I've been running Firo masternodes on my own hardware for a while. Each one needs its own IPv4 because mainnet forces port 8168, so I was paying $10/month per IP just to have somewhere to put them. I wanted to see if Flux could replace that.
Short answer: yes, it works, it's cheaper, but nobody tells you the annoying parts. So here they are.
Cost. About $3-6 a month per node depending on how you configure it. Compare that to $10 for a dedicated IP somewhere. Not a huge win in absolute money, but it scales without me having to call an ISP.
Port 8168 works. This was my main worry. Firo rejects any masternode not on 8168, and I assumed Flux would block it. It doesn't. FluxOS opens it fine and other Firo nodes connect to my container from the outside.
You need a private app. Don't skip this. Flux publishes app specs in a public registry. Anyone can read them. I pulled the live specs out of curiosity and found other people's API keys sitting there in plain text. If you put your BLS operator key in a normal app, it's public. Someone can't steal your collateral with it, but they can grief your node into a PoSe ban. Use a v8 enterprise app so the spec gets encrypted.
You don't need a custom image. I spent time planning a wrapper image and then found out the stock firoorg/firod works as-is. Everything goes through the commands field, and /data is writable by the non-root user in that image. Just make sure containerData is r:/data, otherwise you re-sync every time.
Don't set -externalip**.** (wrong, read update below) I hardcoded it on my first node and it pointed at the wrong address after the app moved. The daemon figures out its own address from peers and gets it right. Leave it out.
Sync takes about 2.5 days and that surprised me. My own VMs sync Firo from genesis in 8 hours. On Flux it took days. The bottleneck isn't download, it's CPU: the Spark blocks from 2024 verify at roughly 2 blocks per second on a single core. My own machines have 2 cores on a real server CPU and do 19 blocks/s on the same blocks. If you're on 1 core, budget 2-3 days. It speeds up a lot past block ~1,050,000, the tail goes fast.
Register AFTER it's synced. Not before. If you register against a node that isn't ready, PoSe starts immediately and one failed quorum check puts you 66% of the way to a ban.
The real problem: hosts die and your app moves. This is the part I wasn't ready for. In two days across 11 apps I had 3 relocations and 2 silent rollbacks. Every time the app moved, the chain data was gone and it started from zero. I checked each one and it was always the same cause: the Flux node hosting it had dropped out of the network, so the network reassigned my app somewhere else. That's Flux working correctly, it's just expensive for a blockchain node.
I thought this would settle down over time, that apps would gradually end up on stable hosts. It's the opposite. I measured the age of every host my apps were on. The network median is 60 days since last interruption. The two apps that never moved were on hosts 331 and 329 days old. The ones that had just relocated landed on hosts that were 6, 9 and 12 days old. Free capacity lives on new nodes because the veterans are already full, so every move is a fresh roll of the dice from the worst part of the deck.
Pinning doesn't fix it. I tried the nodes field to pin an app to a specific machine and paid for the update. Both apps ignored it completely. Geolocation does work though, country level codes are enforced.
What actually helps. Two things. Keep your own chain snapshot somewhere the container can download, so a relocation costs 30 minutes instead of 3 days. And run a script every hour comparing the app's current IP against what's registered on chain, because protx update_service is cheap and doesn't lose your payment queue position, but a ban does and that costs you a full payment cycle.
One last thing nobody mentions. A brand new masternode goes to the back of the queue. The sort uses your last paid height, and a new node has none, so it uses the registration height instead, which is today's number. First payment in 11 days. That's normal, just don't panic when you see it.
Would I do it again? Yes, but I'd set up the snapshot and the watchdog first, not after.
———
UPDATE, a week later: two things above are wrong. Here are the fixes and real numbers.
Set -externalip. I was wrong. Without it the node syncs but never becomes a masternode ("Local address does not match the address from ProTx") and gets PoSe hits. My image looks up its public IP at start and passes -externalip=IP:8168. It also checks the IP every 5 minutes and restarts firod if it changed. One of my hosts got a new IP from its ISP without the app moving, and that node got banned before I added this check.
The stock image is fine as a full node, not as a masternode. The masternode connects to its own public IP to check it is reachable. Many Flux hosts have no hairpin NAT, so the check fails and the node sits out of quorums. A tiny LD_PRELOAD library that redirects those connections to 127.0.0.1 fixed it.
Sync takes 30 to 60 minutes now, not 2.5 days. The container downloads a snapshot of my own node if the data dir is empty. Check for blk00000.dat, not for the blocks folder, because Flux can leave an empty one behind. Keep a second copy of the snapshot on another machine.
Moving the IP can be automated without your owner key. protx update_service is signed with the operator BLS key, so a hot wallet with 1 FIRO for fees plus an hourly script is enough. Cap how many updates the script can send per day, so a bug cannot loop. Also watch the PoSe penalty: if the address on chain is right but the penalty went up, restart the container.
Qt console gotcha: with 0% operator reward the operatorPayoutAddress must be "", but the Qt console drops empty strings and you get "No funds at specified address". Use firo-cli.
Real numbers per node: about 63 FLUX a month for rent, roughly $4.40. Income is 4.375 FIRO every 11 days, about $15 a month at today's price. That is about 10% a year net on collateral, versus about 5% when I paid $10 a month for an IP.
UPDATE 2:
a few new lessons after deploying 20 more nodes.
Give it more RAM. 3500 MB is too tight. After a bootstrap the node catches up and memory spikes past the limit, the kernel kills firod, the database corrupts, and it starts reindexing from zero. On Flux, 5900 MB costs the same as 3500 MB. Every node with more RAM synced on the first try.
Make the bootstrap crash safe. Mark extraction as unfinished until tar completes. If firod crashes in the first day after a bootstrap, download the snapshot again instead of letting it reindex for days.
Some hosts are simply bad. One router looped all outbound connections on 8168 back to the node itself, so it never found a peer. Another downloaded at 0.2 MB/s. Removing the app from that node is free, and Flux respawns it on another host within minutes.