Nightbell

Android · Apache 2.0 · v3.0.2

The uptime monitor that actually wakes you up.

An open-source Android app that runs every check from the phone in your pocket.

No server path
Phone → your service → phone
Tokens stay local
No account. Headers and history live on the device.
Alarm, not a ping
Full-screen page that loops until you acknowledge it

Android 8.0 and up Sideload, not on Play

Checkout API

https://api.example.com/health

Checkout API is down

Connection refused · down for 22m

Last
No response
Average
126 ms
Checks
60
Failed
5

Example readout, replayed 60 checks · 5 min cadence

Forty seconds. Watch it with the sound off if you like, the captions are on the CC button.
Read the video transcript
  1. Most uptime alerts are one more notification you swipe away.
  2. Nightbell runs entirely on your Android phone. No server, no account, no third party.
  3. Four steps to add a monitor. Point it at anything that answers over HTTP.
  4. Every check runs on the phone. Your tokens never touch a third party.
  5. When a check fails, Nightbell doesn’t send you something you can miss.
  6. It takes the whole screen, and the alarm loops until you acknowledge it.
  7. Nightbell. The uptime monitor that needs no server, no account, and actually wakes you up.

The problem

A notification you can swipe away is not an alert.

An ordinary alert

Monitoring service · 03:04

api.example.com is not responding

Tap to open the dashboard

One swipe, and there is no second one.

An urgent Nightbell monitor

Nightbell · ongoing

Checkout API is down

Connection refused · down for 22m

Marked ongoing, so the swipe does nothing. It comes straight back.

Checking a URL has been a solved problem for twenty years. The hard part is the last ten centimetres, between a server that knows and a person who is asleep.

Why I built it instead of paying for one

I kept missing outages because ordinary monitoring notifications behaved like everything else in the shade. The services I tried either wanted a subscription before they would text me, or sent something that looked exactly like a marketing email and got swiped away with one. By the time I noticed, the interesting part of the incident was over.

The hard part is alerting without crying wolf. An outage on an urgent monitor is not a line in the shade. It behaves like an incoming call, and everything else in the app exists to earn the right to do that.

Hosted monitoring, self-hosted monitoring and Nightbell compared across six aspects
Aspect Hosted service like UptimeRobot Self-hosted like Uptime Kuma Nightbell on your own phone
Infrastructure Theirs. Nothing to run. A box, a container, a certificate, and monitoring for the monitor. The phone you already carry.
Account Required. Usually required, at least locally. None. There is nothing to sign up to.
Where tokens sit On their servers. On your server. On the device, in one JSON document.
Paging you Usually the paid tier. Yours to build, on top of a push service. A local alarm, so there is nothing to bill for.
At 3am you get An email or a push notification. Whatever you wired up. A full-screen page and an alarm that loops until you acknowledge it.
It cannot Fail quietly. You will get a bill. Watch itself. Check while the phone is off, or beat Android's 15 minute background floor without a foreground service.

Scroll the table sideways for the Nightbell column

How a check runs

The check leaves your phone and goes to your service. That is the entire path.

The path a Nightbell check takes Your phone sends the check straight to your own service and gets the response back. A second path, from your phone to a monitoring company, is drawn crossed out: there is no such connection, because there is no account and no server in between. Your phone OkHttp, or an offscreen WebView Your service api.example.com A monitoring company no account, no server, no relay in between the check, and the response tokens, URLs, uptime history State stays here: one JSON document in DataStore, exported only when you export it.

Not in the path, and not in the dependency list

  • relay
  • queue
  • worker
  • backend
  • account
  • push service
  • analytics SDK
  • crash reporter
  • icon proxy

What it does not buy you is anonymity from the thing you are watching. Your service sees the request and the IP it came from, exactly as it would for any other client on your network. That is what a check is.

Implementation details

A status or request monitor is an OkHttp call from your phone to the host you typed in. A page-element monitor loads the page in an offscreen WebView on the same phone and reads the DOM nodes you picked by tapping them. There is no relay, no queue and no worker somewhere else, because there is nowhere else.

Everything the app knows lives in one JSON document in DataStore: your monitors, their headers and bodies, their sample history and your settings. Backup and transfer writes that document through the Storage Access Framework, which is why Nightbell asks for no storage permission and why you choose where the file goes. Nothing is uploaded, and there is no import path that could reach anything you did not hand it.

What that does and does not buy you

It buys you this: an API token in a monitor's headers is on your phone and on your server, and nowhere in between. No third party can leak it, subpoena it, or change its terms of service. There is no analytics library, no crash reporter and no backend in the dependency list, and site icons are fetched from the monitored origin itself rather than through an icon proxy.

It does not buy you anonymity from the thing you are watching. Your service sees the request and the IP it came from, exactly as it would for any other client on your network. That is what a check is.

Three kinds of check

Point it at a status code, a response body, or one element on a rendered page.

Status check

The cheap one

Hit a URL, assert on the status code. Four modes, because "is it up" means different things to a health endpoint and to a page that is allowed to redirect.

exact code any 2xx a range any response at all

Cheapest check in the app. A connection refused has no status code and no round trip, so it reports no response and no latency rather than inventing a zero.

Request and response

The precise one

Choose the method, set headers, send a body with its content type, then assert on what comes back. Six body assertions, and JSON paths that understand nesting and array indices.

GET POST PUT PATCH DELETE HEAD
contains does not contain exactly equals matches regex JSON field equals JSON field exists
data.items[0].state

This is the one that catches a deploy that returns 200 and an empty payload, which is the outage a status check is blind to.

Page element

The expensive one

Loads the real page in an embedded WebView and watches DOM nodes you picked by tapping them. Each node carries its own expectation and an optional nickname, and the alert names the one that broke.

element exists element is gone text equals text contains text unchanged attribute equals

Every node resolves against one page load, so watching six costs about what watching one costs. The expensive part is booting the WebView, not the assertions. Selectors are re-resolved in a fixed order, id first, then a stable data attribute, then the shortest unique CSS path, then XPath, then a text fingerprint, so a cosmetic markup change degrades instead of paging you at 4am about a renamed class.

The Nightbell widget at its tall size, one column listing six monitors worst first: Marketing site down, Checkout API at 4100 ms, then four healthy services with their response times. The same widget dragged wider, the six monitors spilling into two columns with a checked just now line underneath. The same widget dragged flat into a single wide row, showing the three worst monitors and nothing else.
There is a widget too, worst monitor first. Monitors flow into columns rather than disappearing below the fold when you drag it flat, because a widget's height is whatever you dragged it to.

Adding a monitor

Four steps, and everything is changeable afterwards.

Step 1 of 4

What to watch

Status check, request and response, or page element. This is the only choice that changes the rest of the wizard.

What step 1 sets: status checkrequest and responsepage element

Step one of the new-monitor wizard: three kinds of check listed as rows, Status check selected with a tick, Request and response, and Page element.

Step 2 of 4

Target

The URL, and for a request monitor the method, headers and body. For a page monitor this is where the live preview opens and you tap the nodes you care about.

What step 2 sets: nameurlmethodheadersbodytest now

Step two: a name field reading Orders service, a URL field reading https://api.example.com/orders/health, a row of methods with GET selected, an empty headers section, and a Test now button.

Step 3 of 4

Expectations

What healthy means. A status mode, a body assertion, or one expectation per watched element. Test now runs the whole thing once, there and then, before you commit to it.

What step 3 sets: status modeexpected codebody assertiontest now

Step three: status code modes with Exact code selected, an expected code of 200 with a grid of common codes, six response body assertions with No body check selected, and a Test now button.

Step 4 of 4

Cadence and alerts

How often, how long to wait for an answer, and whether this monitor is urgent. Also the latency budget, above which a successful but slow answer counts as degraded rather than down.

What step 4 sets: intervaltimeoutfollow redirectslatency budgeturgent

Step four: check every 15 minutes with shortcuts from one minute to twelve hours, a 15 second timeout, follow redirects and active both switched on, a custom latency budget switched off, and a Create monitor button.

Nothing here is locked in. Every field is editable from the monitor's detail screen afterwards, and Test now runs the whole check once before you commit to it.

Urgent alerts

When it breaks, Nightbell wakes you up.

Urgent is a per-monitor switch for the things you cannot afford to sleep through. It is the only part of the app that is allowed to be loud, and it is deliberately hard to ignore.

  1. 01 The check fails

    The failure threshold is met, so this is an outage and not a blip. The card goes red, the banner at the top of the list counts it, and the sparkline keeps the wall of refused connections where you can see it.

  2. 02 The page arrives

    One notification, on a dedicated alarm channel that bypasses Do Not Disturb. It is marked ongoing, so there is no swipe that dismisses it, and if full-screen alerts are granted it wakes the screen rather than waiting politely on the lockscreen. It comes back every five minutes until you deal with it.

  3. 03 You decide one thing

    Ack silences this outage and nothing else. Re-check runs the check again right now. Mute buys an hour. The monitor stays red until it is not, and recovery re-arms the loop so the next outage shouts again.

The Nightbell monitor list with a red banner reading 1 of 5 is down, a Check all now button, a red Price watch card with a sparkline ending in a wall of failures, and an amber Asset CDN card marked degraded.
A phone home screen with the Nightbell urgent alert as a red heads-up card reading Checkout API is down, connection refused, down for 22m, with Ack, Re-check and mute buttons.
The Nightbell monitor detail screen for Price watch: a red dial reading 93 per cent over the past ten hours, a Down verdict, the element text that changed, and Check now, Pause and Mute 1h buttons above a response time chart that ends in red.
A screen recording off a real phone. No sound, and it loops.
Repeat
Every 5 min, until acknowledged
Channel
A dedicated alarm channel that bypasses Do Not Disturb
Swipe away
Not possible. The notification is ongoing
On the page
Acknowledge, Re-check now
On the card
Ack, Re-check, Mute 1h
Acknowledging
Stops this outage only. Recovery re-arms it
Exact acknowledgement behaviour

Acknowledging stops the repeats for that outage and nothing else. The monitor stays red, the ordinary down notification stays where it was, and recovery re-arms the loop so the next outage shouts again. Urgent overrides cooldown and the repeat toggle, because that is the point, but it still honours the master switch, the per-monitor switch, mute and the failure threshold. It means "do not let me miss this", not "ignore everything I configured".

What Android allows

Four grants, and the limits that stay after them.

Android will happily let an app think it is alerting you while delivering nothing. This is the part most phone monitoring apps skip, so it is worth being specific about.

Notifications
Without it nothing is posted at all. Dialog, in the app
Unrestricted battery
Lets checks keep their schedule, and lets an urgent page keep repeating while the screen is off. Dialog, in the app
Full-screen alerts
Lets an urgent page wake the screen instead of waiting on the lockscreen. Android settings, deep-linked
Do Not Disturb access
Without it, Do Not Disturb and Bedtime mode silence urgent pages completely, which is exactly when you want one. Android settings, deep-linked
The Nightbell first-launch setup screen listing four requirements: notifications, unrestricted battery, full-screen alerts, and Do Not Disturb access, with three granted and one outstanding.
The first-launch screen counts what is ready, deep-links to the two toggles Android has no API for, and re-checks itself when you come back.

Things the platform will not do

  • Sub-15-minute background intervals are impossible, not unreliable. WorkManager's periodic minimum is 15 minutes and there is no supported way around it. Strict mode is the only way to get the interval you asked for.
  • Doze can delay even a 15 minute interval. Exempting Nightbell from battery optimisation helps and is not a guarantee. Delay is reported in Settings and never notified about, because it is not an outage.
  • Strict mode costs a permanent notification and real battery. That is Android's price for a real cadence, not a design choice.
  • Element checks need a renderable page. Heavy single-page apps are polled for about five seconds after load; anything slower to hydrate may report element not found.
  • Quiet hours still suppress urgent pages unless you turn on the bypass. That default is wrong. It is the next thing I want to fix properly, with a migration rather than a flipped constant.
  • There is no CI. The 359 JVM and 173 on-device tests are run by hand.

Getting it

One APK, 2.06 MB, signed with a key you can check.

File
Nightbell-3.0.2-release.apk
Needs
Android 8.0+
Licence
Apache 2.0
Store listing
None anywhere
  • GitHub Releases The APK, from the tag it was built at. Available now Open
  • Obtainium Paste the repository URL for updates without a store. Works today Copy the URL
  • F-Droid and IzzyOnDroid Not submitted. IzzyOnDroid first, then F-Droid proper. Not yet

There is no store listing for Nightbell anywhere. If you find one, it is not mine.

# download the APK from the release above, then
adb install -r Nightbell-3.0.2-release.apk

Check what you downloaded

APK SHA-256
6a5a2a858ac3e245657b7c6a284596626a78f58eddf749f3af550f099be11115
Signing certificate SHA-256
20d8abdaa8416a9a751e3ea144ef1523d7ddbaaeee9ec6be01d63a65574a70de
Certificate subject
CN=Nightbell, OU=river, O=river, L=Prague, C=CZ

Signed with my key, not Google's, so Play Protect asks once. The certificate number is what says the next version came from the same person.

Upgrading, and building it yourself

Android refuses an update signed by a different key, which is what makes the certificate number above worth checking before you trust an APK you did not get from the link on this page.

Anything from 2.0.0 onward updates in place and keeps your monitors. 1.x used a different application id, so the only route across is the JSON export in Settings. Building it yourself needs JDK 17 and an Android SDK with API 36; the commands are in the README.

Answers

The questions worth asking before you sideload something.

Does it keep checking when my phone is off?

No. The checks run on the phone, so a phone that is off, out of signal or in flight mode is a monitor that is not checking. That is the honest cost of having no server. If you need something watching while the phone is in a drawer, you need a machine that stays awake, and Nightbell is the wrong tool.

Can it check more often than every 15 minutes?

Only in strict mode. Android’s PeriodicWorkRequest has a hard 15 minute minimum and WorkManager silently clamps anything shorter, so Nightbell clamps it visibly instead and says so in Settings under Checker health. Turning on strict foreground monitoring runs a foreground service that keeps your interval exactly, down to about 15 seconds. It costs a permanent notification and real battery, which is Android’s price for a real cadence and not a design choice.

Why is it not on the Play Store?

There is no Play listing. Grab the APK from GitHub Releases and sideload it. It is signed with my own key, so Play Protect will ask you to confirm the first time. The foreground service also declares specialUse rather than dataSync, because dataSync is capped at six hours a day on API 34 and up, which would quietly break the one guarantee strict mode exists to make. Shipping that through Play review would mean justifying the subtype. Sideloading is unaffected.

Does anything leave my phone?

The check itself, to the service you pointed it at. Nothing else. There is no analytics library, no crash reporter and no backend in the dependency list, the permission list is short enough to read in one go, and site icons are fetched from the monitored origin rather than through an icon proxy. Your service sees the request and your IP, the same as any other client on your network would. What never happens is a third party seeing your tokens, your URLs or your uptime history.

Can I watch something on my home network, or over plain HTTP?

Yes. Cleartext traffic is allowed on purpose, so a box on your LAN answering on http:// is a valid target. The catch is the same one everywhere else: the check comes from the phone, so the phone has to be able to reach the host. A monitor on a LAN address stops passing the moment you leave the house, which is a true fact about your network rather than a bug.

What happens to my monitors when I update?

Anything from 3.0.1 onward installs over the top and keeps your monitors, history and settings. 3.0.0 is the exception: 3.0.1 is signed with a new key, because the old certificate’s subject still carried the previous name and a subject cannot be edited, so a 3.0.0 install has to be uninstalled rather than updated. Coming from Pulse 2.x is different again: 3.0.0 renamed the app and moved its application id, which Android treats as a different app entirely, so it installs beside the old one with an empty data directory rather than upgrading it. The route across is Settings, Backup and transfer in 2.4.3, then import on the new install. Export before uninstalling anything, because once the old app is gone so is its data. Placed widgets and your notification channel settings do not come across either.

How is this different from UptimeRobot or Uptime Kuma?

Both are better than Nightbell at being a monitoring service: they keep watching when your phone is in a drawer, they hold years of history, they have status pages. Nightbell is not trying to win that. It is trying to be the thing that wakes you at 3am without a subscription, a server or an account, by running on hardware you already carry and paging you with a local alarm instead of a push notification. If you already run Uptime Kuma, Nightbell is a reasonable pager to put in front of it.

Will the alarm really get through Do Not Disturb?

Once you grant Do Not Disturb access, yes: urgent alerts go to a dedicated DND bypassing alarm channel. One thing to know first, because it is the current known bug rather than a feature: quiet hours still suppress urgent pages unless you turn on the bypass. That default is wrong and is the next thing on my list, with a migration rather than a flipped constant.

What does it do to my battery?

With the default WorkManager scheduling, close to nothing. Strict foreground monitoring costs real battery, because it wakes for every due check instead of letting Android batch them, and it holds a permanent notification while it does. An unacknowledged urgent outage starts that same service on its own regardless of the setting, and stops it the moment you acknowledge or the monitor recovers.

Is it actually free?

Apache 2.0, the whole thing, source included. No paid tier, no account, no upsell, nothing to cancel. Same licence as everything it depends on, so forking it or lifting a piece of it is straightforward. Keep the notice and say what you changed.