Server documentation
Everything you need to run NestHealth at home: installing, adding your family, connecting phones, and keeping it backed up.
Overview
The NestHealth server is a single small program. It stores your family's records in a SQLite database and serves:
- the API that the NestHealth iPhone app uses when you choose Share with the family, and
- a web app with the same features, for any browser.
It has no dependencies beyond Node.js, and it never contacts the developer. It only talks to the push services and webhooks you set up.
The server and web app are free software under the GNU Affero General Public License v3.0. You can use, study, change and share them. If you modify them and let other people use your version over a network, you must offer those people your modified source code. The licence doesn't cover the NestHealth name or icon, so please give forks their own.
Requirements
- An always-on computer on your home network: a home server, NAS, Raspberry Pi or old laptop. Linux, macOS and Windows all work.
- Docker (recommended), or Node.js 22.13 or newer.
- Very little storage: the Docker image is about 60 MB, and records take a few megabytes (more if you add photos).
Install with Docker
The server is published on Docker Hub as hansford909/nesthealth-server, for ordinary PCs and NAS boxes (amd64) and Raspberry Pi (arm64).
- Make a folder for it and download the Docker Compose file:
mkdir nesthealth && cd nesthealth curl -O https://nesthealth.soam.uk/download/docker-compose.yml - Start it. Docker downloads the image and restarts the server automatically after a reboot:
docker compose up -d - Open
http://<server-address>:8080in a browser, for examplehttp://192.168.1.20:8080, and create the first account.
Records and photos are kept in a Docker volume called nesthealth-data, so they survive updates.
Without a compose file
docker run -d --name nesthealth --restart unless-stopped -p 8080:8080 \
-e TZ=Europe/London -v nesthealth-data:/data hansford909/nesthealth-server
This also works in NAS apps such as Synology Container Manager, Unraid or Portainer: use the image hansford909/nesthealth-server, port 8080, and a volume or folder mounted at /data.
Building from source
Prefer to build it yourself? Download the source or clone it from GitHub, then run docker compose up -d --build in that folder after changing image: to build: . in docker-compose.yml.
Different port? If 8080 is already taken, change the ports line in docker-compose.yml to, for example, "8090:8080", and use port 8090 instead.
Install without Docker
With Node.js 22.13 or newer installed:
git clone https://github.com/mahansford/nesthealth-server.git
cd nesthealth-server
node server.js
Records are stored in a data folder next to server.js. Set DATA_DIR to keep them somewhere else, and PORT to use a different port. To keep it running in the background, use your system's service manager (for example a systemd service or a launchd agent).
Accounts
- First account: the first person to open a new server creates the admin account with a name, email and password.
- Adding the family: the admin adds each parent or carer under Settings → Family Accounts. Each gets a temporary password and chooses their own the first time they sign in.
- Resetting a password: the admin can reset anyone's password from the same screen. That signs them out everywhere.
- Passkeys: in the web app, over HTTPS with a domain name, you can add a passkey (Face ID, Touch ID or your screen lock) under Settings → Account.
- Deleting an account: Settings → Account → Delete Account. The last account can also erase all of the family's records.
Headless setup: set ADMIN_EMAIL and ADMIN_PASSWORD (and optionally ADMIN_NAME) to create the admin account when the server first starts. Remove them afterwards.
The iPhone app
- Open NestHealth and choose Share with the family. If you've been using Just this iPhone, go to Settings → Data → Connect to a Home Server instead. Your records can be copied across.
- Enter the server's address including the port, for example
http://192.168.1.20:8080, or its HTTPS address. - Sign in with your account.
Reminders for when a dose can be given again are scheduled on each iPhone, so they work on any server. Widgets and the Lock Screen countdown work too.
Two limits on your own server: instant alerts when another parent logs a dose or a fever, and passkeys in the app, rely on Apple keys that belong to the app. In the iPhone app you'll sign in with your password. For alerts, turn on web push or a webhook such as ntfy.
The web app
Open the server's address in any browser to use NestHealth on a computer, an Android phone or a tablet.
- iPhone and iPad: open it in Safari, then Share → Add to Home Screen. It opens full screen, like an app.
- Android: open it in Chrome, then ⋮ → Add to Home screen (or Install app).
HTTPS and remote access
On your home network, plain http:// works for logging and syncing. HTTPS is needed for web push notifications and passkeys, and a VPN or tunnel lets you use NestHealth away from home. Any of these work:
- Tailscale (easiest):
tailscale serve --bg 8080gives youhttps://<machine>.<tailnet>.ts.net, and it works away from home on any device signed in to your tailnet. - A reverse proxy such as Caddy, Nginx Proxy Manager or Traefik, with a real certificate for a domain you own.
- Cloudflare Tunnel. This makes the server reachable from the internet, so put Cloudflare Access in front of it.
Behind a proxy, set PUBLIC_URL to the HTTPS address people use (for example https://health.example.com) so passkeys work.
Notifications
Set these up in the web app under Settings → Notifications. Notify About chooses the events; the settings apply to every device and webhook.
| Event | Sent when |
|---|---|
dose_due | A medicine can be given again |
dose_given | Another parent logs a dose (you're not notified about your own) |
fever | A reading is at or above your fever threshold |
check_temp | It's time to recheck a temperature |
test | You tap Send Test |
Web push
Works on iPhone (iOS 16.4 or later, from the Home Screen app), Android and desktop browsers. It needs HTTPS. In the web app, go to Settings → Notifications and turn on push notifications for this device.
Webhooks
Choose Add Webhook and pick a format: ntfy, Discord, Slack or JSON (for Home Assistant, Node-RED or n8n). The JSON format posts:
{
"event": "dose_given",
"title": "Ella had Children's paracetamol",
"message": "5 ml given at 14:05 by Mum.",
"person": "Ella",
"time": "2026-09-25T13:05:00.000Z",
"data": {}
}
Backups
Back up everything (records, photos and settings) from the folder where you want the backup saved:
docker run --rm -v nesthealth-data:/data -v "$PWD":/backup alpine \
tar czf /backup/nesthealth-backup.tgz -C /data .
To restore, stop the server, restore into the volume, and start it again:
docker compose down
docker run --rm -v nesthealth-data:/data -v "$PWD":/backup alpine \
sh -c "cd /data && tar xzf /backup/nesthealth-backup.tgz"
docker compose up -d
Without Docker, copy the data folder while the server is stopped. You can also export every record as a spreadsheet from Settings → Data → Export all records (CSV).
Updating
From the folder with your docker-compose.yml:
docker compose pull
docker compose up -d
Your records live in the nesthealth-data volume, so they're kept. To stay on one version, use a tag such as hansford909/nesthealth-server:1.0 instead of latest.
If you build from source or run it without Docker, update the code with git pull, then run docker compose up -d --build or restart node server.js.
Configuration
Set these as environment variables, in the environment section of docker-compose.yml.
| Variable | Default | What it does |
|---|---|---|
PORT | 8080 | The port the server listens on |
DATA_DIR | ./data (/data in Docker) | Where the database and photos are stored |
TZ | Europe/London | Time zone for times shown in notifications |
PUBLIC_URL | From the request | The HTTPS address people use. Set it if passkeys fail behind a proxy |
VAPID_SUBJECT | A placeholder address | A contact address (mailto:[email protected]) sent to web push services |
ADMIN_EMAIL, ADMIN_PASSWORD, ADMIN_NAME | Not set | Create the first admin account on start-up |
Security
- Everything (records, photos and exports) requires signing in.
- Passwords are stored as scrypt hashes. Sessions are random tokens, stored hashed, that last 90 days from last use.
- After 8 failed sign-ins for an account, or 30 from one address, sign-in is paused for 15 minutes.
- Keep the server on your home network or a VPN such as Tailscale rather than exposing it directly to the internet. If you do expose it, put an access layer such as Cloudflare Access in front.
- The settings passcode in the app is a child lock, not a security feature.
Troubleshooting
The app says it can't reach the server
Check that the address includes the port (http://192.168.1.20:8080), that your phone is on the same network or VPN, and that the server is running: docker compose ps. Opening http://<server-address>:8080/healthz in a browser should show ok.
The iPhone app isn't getting notifications
In the app, open Settings → Notifications and tap Turn On Notifications if it's there (NestHealth only appears in the iPhone's own notification settings once it has asked). Send Test Notification goes only to the devices of the person who pressed it, never the rest of the family.
Web push notifications don't arrive on iPhone
Web push on iPhone needs iOS 16.4 or later, the web app opened from the Home Screen (not in Safari), and an HTTPS address. Use Send Test in Settings → Notifications to check.
There's no option to add a passkey
Passkeys need HTTPS with a domain name, not an IP address. Browsers don't allow them for plain http:// or IP addresses. In the iPhone app, sign in with your password.
“Too many attempts”
Sign-in is paused for 15 minutes after repeated wrong passwords. Wait, then try again, or ask the admin to reset your password.
Seeing the server's log
docker compose logs -f
Still stuck? Get in touch.