It sounds like you’ve tested the simple failure modes, machines dropping off altogether, which is good; but I’d still be sceptical about the system’s robustness, because those aren’t the interesting failure modes, and the interesting failure modes are the ones that give the most trouble. Systems are easy when a node is there or not there, but when a node becomes unreliable (e.g. overloaded or network troubles of a variety of kinds), that’s when the real trouble happens, and when it’s most common that measures that were supposed to make things available actually make them unavailable. (This stuff is hard.) That’s why avoiding a distributed architecture until you actually need it can be well worthwhile.
I would use something like Caddy which manages it's own config and has an API for live updates.
SQLite for the user management and administration.
I would possibly have 2 Caddy nodes though, with a LB between them (instead of my potentially flawed heartbeat floating IP mechanism). The application would do an atomic update to both Caddy servers, if either failed, it would roll it back.
But you are still looking at around $30-40/mo for hosting, not saving much money, but making it much simpler and saving headaches.
It was overly complicated for robustness. I tested multiple database crashes, servers going down in one zone, and it would always fail over just fine, because application code would connect to a slave if it could not connect to the master, so forwards would work, unless there were multiple outages in multiple data centers.
Now switching the floating IP to the backup forwarder did take about 5 seconds, and I could have made that better by putting a proxy in front, but I'm just moving the point of failure up a level, and it was never needed anyway.