Nam Nguyen
Back to blog

How I monitor my own servers without Grafana

7 min readNam Nguyen

I run two boxes for my own products. One is an Oracle Cloud ARM instance that serves BiBoTracking and BiBoReward in production. The other is an old Mac sitting in my apartment that does staging, a few static sites, and whatever else I need a public URL for. Between them they hold everything I've built outside of my day job.

For a long time the way I knew whether they were alive was: a customer told me, or I opened a browser and looked.

Then I did the thing everybody does. I installed the stack.

The stack I installed, and the stack I deleted

Prometheus, node_exporter, Grafana. It took an afternoon to get running and it genuinely looked incredible. Dark dashboards, CPU curves, memory, disk, per-endpoint latency histograms. I sent a screenshot to a friend. I felt like I had operations.

What I actually had was a second system to maintain.

The problems were all small and all boring. Grafana and Prometheus together ate a slice of RAM that mattered on a small box — not catastrophic, but enough that I was watching the watcher. Config lived in several files across two formats, and every time I added a service I had to remember which one. Upgrades broke dashboards. Retention needed thinking about. The Oracle box has no open ports except SSH, by design, so exposing anything at all meant tunnels and reverse proxies and auth I had to get right.

And here's the part that finally killed it: in the months I ran that stack, I did not once find out about an incident from Grafana. Not once. Every real problem reached me some other way — a failed request in my own app logs, a user message, me poking around. Grafana was where I went after I already knew something was wrong, to look at a graph and nod.

A dashboard answers "what is happening right now." I almost never have that question. The question I have is "is anything broken, and should I stop what I'm doing." Those are different products.

On 2026-07-05 I deleted the stack and replaced it with a single Go binary I wrote. I call it bibomon. It is the most useful thing I built this year and it is barely any code.

What the small binary actually does

It's one binary, one config file, one SQLite database. It runs on the Mac. It collects host metrics from both boxes, pings my endpoints, reads service states, keeps a rolling 30 days of logs that I can filter in the browser, and serves one password-protected page.

Data from production gets to it over the SSH reverse tunnel that's already there. That was the unlock, honestly. Because I wasn't trying to run a general-purpose observability platform, I didn't need an agent protocol or a scrape endpoint or a port. I needed a number to travel over a connection I'd already opened. The constraint that made the proper stack annoying made the small tool simpler.

What I did not build: no query language, no alert rule DSL, no plugins, no multi-user accounts, no templating, no retention policy beyond "delete rows older than 30 days." One login. Mine.

That's the whole trade. Grafana is built so that any team can express any question about any system. I am one person with two boxes and a short list of things that can go wrong. I know that list. I can hard-code it. A self hosted monitoring tool that fits in your head is worth more than a flexible one you have to relearn every time you open it.

I wrote about this one in the shipping list for last month, along with everything else that went out — what I shipped in October 2026 is the honest version of a month, and the monitoring work is a small line in it, which is about right.

The alerts that actually matter

Everything above is plumbing. The part that changed how I work is a Telegram bot that messages me, and the short list of things it's allowed to message me about.

There are four that have ever been worth waking up for:

The endpoint is down

Not "CPU is high." Not "memory is at 80%." A real HTTP request to the real public URL came back wrong or didn't come back. This is the only alert that maps directly to "a customer cannot use the thing." Everything else is a theory about why.

The box is unreachable

Separate from the above, because the fix is completely different. One means my app died. The other means the machine, the network, or the tunnel died, and I should stop looking at code.

A service isn't running

The Go backends run under systemd on production and launchd on the Mac. A process that exited and didn't come back is the single most common real failure I've had, and it's trivially detectable. Catching this before a user notices is most of the value of the whole project.

The 5xx rate jumped

The only statistical one. Not a latency percentile, not a histogram — just "is my API returning errors to real requests more than it normally does." It's crude and it has fired on things that didn't matter. I keep it anyway, because twice it told me about a bad deploy before anything else did.

Disk and memory alerts exist too, but they're a different category: they're chores, not emergencies. I want to see them in the morning, not at midnight.

What's striking is how short the list is. I spent an afternoon building beautiful dashboards for metrics I have never once acted on. The alerts that matter all answer a yes/no question about whether something a person depends on is working.

What it cost, and what I'd do differently

The honest cost: a weekend to build, and a long tail of small evenings since — adding services, fixing a keepalive that silently stopped reporting, making logs filterable because grep over SSH got old. It's not free. I own this code now, and when it breaks nobody has written a Stack Overflow answer about it.

I also lost things. I have no historical graphs worth looking at beyond a month. I can't ask a question I didn't think of in advance. If a second person joined and needed access, I'd have real work to do. Those are genuine losses and I'd be lying to call them features.

What I'd do differently: I'd write the alerts first and the dashboard second, or maybe not at all for a while. The dashboard is the fun part, so I built it first, and it's the part I use least. The Telegram messages are what actually changed my behaviour, and they were the last thing I added.

The other thing I'd keep in mind — and this is the same lesson I keep relearning from running small products end to end, the one I wrote about in building BiBoTracking — is that the generic tool is sized for a problem bigger than mine. That's not a criticism of Grafana. If I had thirty services and four engineers on call, I would install it again tomorrow and be grateful. I have two boxes and myself. Matching the tool to the actual size of the problem was the whole decision, and it's the one I got right.