The app is feature complete and works as intended for tracking domain registration and SSL certificate expiry. Feel free to use it, but as of now, no new features will be added. ✌️
A self-hosted dashboard for monitoring domain registration and SSL certificate expiry, with scheduled checks, configurable alerts, and SQLite storage - deployable as a single Docker container.
Table of Contents
Domain Watch is a self-hosted dashboard for tracking domain registration and SSL certificate expiry. Add the domains you care about and the app keeps an eye on renewal dates, surfaces what needs attention, and runs checks on a schedule you control.
No external database required - everything runs in a single Node.js process with SQLite.
- better-sqlite3 — embedded SQLite storage
- whoiser — WHOIS registration lookups
- Node.js
tls— live SSL certificate checks - RDAP over HTTPS — registration lookup fallback when WHOIS fails
- Add domains individually or in bulk, with optional alias, client/description, notes, and renewal link
- Optional SSL monitoring per domain (disable for parked or non-HTTPS domains)
- Hide domains without deleting them
- Snooze alerts while you renew
- Manual registration expiry date when WHOIS/RDAP is unavailable
- Mark domains as renewed (with undo)
- Per-domain check interval override (24h, 48h, 72h, or weekly — or use the global default)
- Groups with custom sort order (drag-and-drop in custom sort mode)
- Collapsible group sections
- Group filter and search across domains, aliases, and descriptions
- Group defaults for renewal links and description prefixes
- Bulk actions: move to group, snooze, hide/unhide, toggle SSL monitoring
- Automatic checks on a configurable interval (1–24 hours, default 24h)
- SSL via live TLS handshake (issuer, expiry, hostname mismatch detection)
- Registration expiry via WHOIS with RDAP fallback
- 24-hour cache for successful registration lookups (reduces registry rate limits)
- Manual “Check now” per domain, per group, or for everything
- Configurable warning/critical day thresholds
- Configurable “check failures” threshold (days without a successful check)
- “Needs attention” panel for expiring/critical domains
- “Check failures” panel for domains past the failing-check threshold
- Scheduler health banner when global checks may have stopped (dismissible)
- “Expiring this month” calendar panel (SSL + domain dates)
- Stat cards: total, SSL valid %, expiring soon, critical, healthy, average days left
- Registrar and SSL issuer breakdown panels
- Status filters, smart filters (lookup failed, manual expiry, SSL off, snoozed, renewed), and multiple sort modes
- Customize dashboard (grid icon): show/hide and reorder panels, stat cards, toolbar items, and domain list options
- Custom accent colors for light and dark mode
- Domain detail panel with check history chart, renewal detection, and lookup freshness
- Privacy mode (masks domain names in the UI)
- Light/dark theme
- Export full backup as JSON (v3, includes check history) or spreadsheet-friendly CSV
- Import JSON with merge (skip duplicates) or replace all data (with confirmation)
- Import preview before applying; v2/v3 backups supported (
nicknameoraliasfield) - Settings included in JSON export
- Single Docker container with health check
- PWA support (installable; offline shell and cached domain/group data via service worker)
GET /api/healthfor Docker HEALTHCHECK and external uptime monitors
- Run the following command
docker compose up -d --build- Open http://localhost:3000.
Data is stored in the named Docker volume domain_data, so it survives container restarts and rebuilds.
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Port the app listens on inside the container |
DATA_DIR |
/app/data |
Directory for the SQLite database |
NODE_ENV |
production |
Set automatically in the image |
Check interval, alert thresholds, timezone, failing-check days, and other runtime options are configured in the app under Settings (gear icon). They are stored in SQLite and persist across restarts. Dashboard layout and theme accents are stored in the browser (localStorage).
The compose file includes a health check that polls GET /api/health every 30 seconds.
services:
domain-tracker:
ports:
- "8080:3000"Requires Node.js 18+ (the Docker image uses Node 20).
- Install dependencies and start the server:
npm install
npm startThe app listens on http://localhost:3000 by default. The database is stored in ./data unless you set DATA_DIR.
- To store the database elsewhere (optional):
DATA_DIR=/var/lib/domain-watch npm startOpens a TLS connection to domain:443, reads the certificate, and computes days until expiry. Also records the issuer and flags hostname mismatches.
- Queries WHOIS (throttled to avoid hammering registries)
- Falls back to RDAP if WHOIS fails
- Caches successful lookups for 24 hours on scheduled runs
Manual checks (“Check now”, “Check all”) always bypass the cache so renewals are picked up immediately.
If both WHOIS and RDAP fail, you can set a manual expiry date. The domain’s SSL status is still tracked normally.
- Scheduled: runs every N hours (configured in Settings), uses registration cache, respects per-domain check intervals, and staggers domains to avoid rate limits
- Manual: forces a fresh registration lookup and runs immediately
Failed checks store the error on the domain row. The UI shows the error inline - stale data is not presented as current.
| Setting | Default | Description |
|---|---|---|
| Check interval | 24 hours | How often scheduled checks run (min 1 hour) |
| Warning threshold | 30 days | Days left before “expiring” status |
| Critical threshold | 7 days | Days left before “critical” status |
| Failing check days | 7 days | Flag domains with no successful check in this many days |
| Time format | 24-hour | 12-hour AM/PM also available |
| Timezone | Browser default | Used for displayed dates |
| Check history max | 30 per domain | Rolling log size |
| Check history retention | 90 days | How long log entries are kept |
| Endpoint | Description |
|---|---|
GET /api/health |
{ ok, lastCheck, domainCount } — for monitoring |
GET /api/meta |
Scheduler info, thresholds, schedulerStale flag |
GET /api/domains |
All domains |
GET /api/domains/failing |
Domains with failing checks |
GET /api/stats |
Dashboard statistics (includes registrar/issuer breakdown) |
PATCH /api/domains/bulk |
Bulk update domains (ids, groupId, snoozeDays, isHidden, monitorSsl) |
GET /api/export |
Full JSON backup (v3: domains, groups, settings, check history) |
GET /api/export/csv |
CSV export |
POST /api/import/preview |
Preview import counts before applying |
POST /api/import |
Import backup (mode: "merge" or "replace") |
POST /api/check-all |
Run checks now (optional groupId) |
All other CRUD routes for domains, groups, settings, and history are available under /api/.
- No authentication — intended for trusted local or private network use. Do not expose directly to the internet without a reverse proxy and access control.
- WHOIS/RDAP limits — some registries rate-limit or hide expiry dates (especially with privacy protection). Manual expiry and the registration cache help here.
- Renewal detection — if expiry jumps by 60+ days between checks, a renewal is auto-detected and surfaced in the detail panel.
Distributed under the GPL-3.0 License. See LICENSE.txt for more information.
