I ran servers before I built apps, and the thing about running servers is that they break when you’re not at a desk. The old sequence — laptop, VPN, terminal, SSH, htop — is fine at 2 PM. At 2 AM it’s a two-minute ritual performed half-asleep, and by the time you’re looking at load averages you’ve usually lost the context that would have told you what happened.
The tools on my phone didn’t help. A terminal app gives you a shell; what I wanted was to know which server needed a shell, and why, before I opened one. That gap is Cura.
Table of contents
Open Table of contents
The design decision that shaped everything: no agent
Every serious monitoring product wants you to install something on the server — an exporter, a daemon, a sidecar. That’s the right call for a fleet. For the three-to-twenty boxes a solo developer or a small team runs, it’s a second system to update, secure and forget about.
Cura reads everything over the SSH connection it already has. On connect it runs a small batch of commands and parses the output: /proc/stat for CPU, /proc/meminfo for memory, df for disks, ip -s link for interfaces, the process table for the top consumers, docker ps and podman ps if they exist, systemctl --failed. That’s the same thing you’d do by hand; the app just does it every N seconds and draws charts.
The constraint this imposes is real: I can only show what a normal user shell can see. But it means Cura works on anything you can SSH into — a Hetzner VPS, a Raspberry Pi, an EC2 box behind a bastion — with zero setup on the server side, and there is nothing to uninstall when you stop using it.
The health score
The dashboard needs a single number per server so you can scan a list and see the one that’s wrong. Cura’s health score is a weighted composite: 35% CPU, 25% RAM, 25% disk, 15% load average, with steep penalties once any component crosses 80%. The weights came from watching which metric actually predicted the outages I’d had. Disk fills are silent until they’re fatal, so disk is weighted as heavily as memory; load average is a lagging confirmation rather than a leading indicator, so it’s the lightest.
It’s not science. It’s a heuristic that makes a degrading box turn yellow before it turns into a page.
Runbooks, or: stop typing on a phone at 3 AM
The feature I use most is the least glamorous. A runbook is a saved, multi-line command sequence that runs with one tap. Cura ships with a few — a health check (uptime, free -h, df -h, systemctl --failed), top consumers, a stress test — and you write your own.
Two details mattered. First, output is captured per command, not as one wall of text. Second, anything destructive (rm, shutdown, reboot, kill, --force) requires confirmation before it runs. A runbook you can execute with a thumb while half-awake needs a guard rail, and that guard rail should be in the app, not in your memory.
The terminal had to be real
It would have been easy to ship a “command runner” and call it a terminal. It would also have been useless the first time someone needed vim or htop. Cura’s terminal is a proper VT100/xterm emulator with session persistence and automatic reconnection. The modifier bar at the bottom — ESC, CTRL, ALT, TAB, HOME, END, PgUp, PgDn, arrows — exists because a phone keyboard has none of those keys and every interactive Linux program needs at least two of them.
On Android the SSH implementation is SSHJ with BouncyCastle and EdDSA; on iOS it’s native. Password, Ed25519 and RSA keys. Credentials go in the Keychain or Keystore and nowhere else — there is no Saltserv server in the path.
The bug that taught me about JCE provider order
This one cost a day and is worth writing down because I’ve since seen other people hit it.
When I integrated RevenueCat on Android, purchases started failing with TLS handshake errors — but only RevenueCat, and only in release builds. Nothing in the purchase code had changed. Everything else in the app, including SSH, was fine.
The cause was the SSH stack. To get EdDSA key support, the app was inserting BouncyCastle into the Java Cryptography Extension provider list at priority 1:
// what the SSH library's setup guide suggested
Security.insertProviderAt(BouncyCastleProvider(), 1)
That makes BouncyCastle the first provider consulted for every cryptographic operation in the process, including the TLS handshake HttpsURLConnection and OkHttp use — and Android’s platform TLS provider is the one that knows about the system trust store and the hardware-backed keys. With BouncyCastle in front, HTTPS silently broke for every SDK that happened to make a request after the provider was registered. RevenueCat was just the first one to notice.
The fix is one word:
Security.addProvider(BouncyCastleProvider()) // append, don't insert at 1
SSHJ finds BouncyCastle by name when it needs EdDSA; nothing else in the app has to know it exists. If your Android app ships an SSH, PGP or crypto library and something unrelated starts failing TLS, check the provider order first.
Alerts that work when the app is dead
A monitor that only works while you’re looking at it isn’t a monitor. Cura runs threshold checks in the background — WorkManager on Android, background tasks on iOS — and sends a local notification when CPU, RAM, disk, uptime or a certificate crosses your line. The notification includes process-level context, because “CPU is high” is not an alert, “CPU is high and it’s node at 340%” is.
SSL expiry tracking is in there because I have personally been burned by a forgotten Let’s Encrypt renewal. It checks 443 and 8443 and puts the days-remaining on the server card, on the front page, where I’ll see it.
Why Pro is a one-time purchase
The free tier is two servers with the full terminal and basic monitoring. Cura Pro — unlimited servers, alerts, SSL tracking, custom runbooks, uptime checks, the widget — is a single purchase.
I could make more money with a subscription. I don’t want to run a subscription business on top of people’s server credentials, and I don’t think a tool that reads /proc over a connection you pay for should charge rent. You pay once; the app works until you delete it. That’s also why there’s no Saltserv account: nothing to lock you out of.
What I’d do differently
- Ship the iOS version sooner. Cura was Android-first for a year because that’s the phone I had in my pocket; the iOS port is now the one with more downloads.
- Put the health-score weights in settings from day one. People run very different workloads and a database box has a different definition of “fine” than a web node.
- Localise earlier. Cura went from one language to twenty in one release, and the strings that were hard to translate were the ones that should have been rewritten anyway.
If you administer Linux machines and you’ve ever fixed one from a car park, Cura is on the App Store and Google Play. The free tier is enough to decide if it fits your hands.