Real-time POCSAG & FLEX pager monitoring β from RF to your browser in seconds
Plug in an RTL-SDR dongle, set your frequency, and get a live dashboard of every pager message on the air.
Quick Start Β· Features Β· Screenshots Β· Installation Β· Docker
PagerMonitor turns a cheap RTL-SDR USB dongle (~β¬25) into a full pager monitoring station. It decodes POCSAG and FLEX transmissions in real time, stores them in a searchable database, and serves a polished web dashboard that works on any device β phone, tablet, desktop.
Built for emergency services monitoring, amateur radio enthusiasts, and anyone curious about what's being paged in their area.
RTL-SDR dongle β rtl_fm β multimon-ng β Node.js β Browser (WebSocket)
- Real-time WebSocket feed β messages appear instantly, no refresh
- Per-user NEW badge β tracks what you've already seen, synced across all your devices
- Highlight rules β regex or text patterns that colour-code matching messages
- Keyword alerts β flash the browser and trigger notifications for urgent keywords
- Click to expand β full details, raw data, timestamp on any message
- Filter by capcode, alias, or group with one click
- Pagination + load more β browse the full history, not just the last 200
Define exactly which messages the system processes. Filtered messages are completely dropped β not saved to the database, not shown in any view, and no notifications are sent.
| Mode | Behaviour |
|---|---|
| Accept all | Default β every received message is processed normally |
| Ignore capcodes | Blacklist β drop messages from specific capcodes, accept everything else |
| Only capcodes | Whitelist β accept only the listed capcodes, drop everything else |
| Only groups | Accept only messages whose alias belongs to one of the selected groups |
| Only aliased | Accept only capcodes that have an alias defined; optionally restrict to specific ones |
Configure under Admin β Messages β Feed Filter. Changes take effect immediately with no restart required.
- Pins for messages with GPS coordinates extracted from message text
- Three modes: individual pins Β· clustered Β· heatmap
- Fly-to animation when clicking the map button on any message row
- Re-geocode button on every message β retries address extraction and pins the result on the map
- Delete location β remove a coordinate pin directly from the map popup
- Reset Map button β clears all current pins from the map without reloading the page
Converts extracted addresses into GPS coordinates. Two backends are available:
| Backend | Cost | Notes |
|---|---|---|
| Nominatim | Free / no key | Default β OpenStreetMap data; good global coverage |
| HERE | Free 250k/month | Better house-number accuracy, especially for NZ, AU, and countries with sparse OSM data |
Configure under Admin β Site β AI Geocode β Geocoder backend. For HERE, generate a free API key at developer.here.com (no credit card required) and paste it in the UI, or set HERE_API_KEY=β¦ in your .env.
When enabled, the raw pager message text is sent to an AI model to extract the street, house number, and settlement before falling back to the built-in regex pipeline β useful for unusual or abbreviated address formats that regex misses.
| Provider | Cost | Notes |
|---|---|---|
| Groq | Free tier (14 400 req/day) | Llama 3.1 8B Instant β fastest option; no local hardware needed |
| OpenAI | Paid (GPT-4o-mini) | Most accurate; requires an API key |
| Ollama | Free / local | Runs on the same Raspberry Pi; no internet required. Llama 3.2 1B fits in 2 GB RAM |
Configure under Admin β Site β AI Geocode. API keys are stored server-side and never sent to the browser. Disabling AI falls back silently to the regex pipeline with no data loss.
Define regex find-and-replace rules that are applied to every decoded message before it is stored or geocoded. Useful for stripping noise, fixing encoding artefacts, or standardising abbreviations across your feed. Includes a live preview so you can test a rule against sample text before saving.
Configure under Admin β Messages β Message Normalizations.
- Give capcodes friendly names:
1234567βFire Station Alpha - Organise aliases into groups and subgroups with colour coding
- Per-alias and per-group row colours and notification sounds
- CSV import/export β manage hundreds of aliases in a spreadsheet
- Any user can add notes to any message
- Notes can be shared (all users see it) or private (only you)
- Note count badge on each message row
- Admins can delete any note
| Service | Features |
|---|---|
| Browser push | OS-level notification on any subscribed device, even with the app closed |
| Discord | Rich embeds β alias, group, Google Maps link |
| Telegram | MarkdownV2 formatted, inline Maps link |
| Gotify | Self-hosted push, any priority |
| Pushover | Native Maps URL button in the app |
| Email (SMTP) | HTML formatted, Maps button, any SMTP provider |
| Webhooks | HTTP POST to any endpoint, HMAC-SHA256 signed |
Per-user notification filters β email and push each have independent filters per user (by group, alias, capcode, or keyword). The global filter on the Services page applies only to Discord, Telegram, Gotify, Pushover, and MQTT. Set preferences from the profile panel.
Install PagerMonitor directly to your home screen on Android, iOS, or desktop. No app store needed.
- Standalone window β no browser bar, feels like a native app
- Background push notifications β alerted even when the phone is locked or the app is closed
- Click the bell icon in the header to enable β automatically subscribes the device to push
| Role | Access |
|---|---|
admin |
Full access β all settings, users, SDR control |
editor |
Aliases, groups, highlights, keyword alerts |
viewer |
Read-only feed, map, archive, search |
- Password reset via email (time-limited link)
- Session tokens with 7-day expiry
Run multiple RTL-SDR dongles in parallel β each on its own frequency, protocol, or gain setting. Status bar shows per-dongle health with individual indicators and hover tooltips.
- Auto-archive old messages to a separate
archive.db - Full-text search across both live and archived messages
- CSV export from the archive panel
- Backup & restore as a single
.pmbackupfile
| Group | Tabs |
|---|---|
| SDR | SDR Control (start/stop/restart) Β· Dead Air detection Β· Live log viewer Β· SDR Clients dashboard Β· Client Key |
| Messages | Database tools (purge, export) Β· Archive config Β· Statistics dashboard Β· Dedup Β· Highlight rules Β· Keyword alerts Β· Feed Filter Β· Message Normalizations |
| Notifications | Services (Discord / Telegram / Gotify / Pushover / MQTT) Β· Webhooks Β· Email (SMTP) Β· User notification preferences |
| Aliases & Groups | Group manager Β· Alias manager (with CSV import/export) |
| System | System stats Β· One-click update Β· Backup & Restore Β· Audit log |
| Site | Site settings Β· AI Geocode (Nominatim / HERE Β· Groq / OpenAI / Ollama) Β· User management |
# Prerequisites (multimon-ng latest is built from source automatically by install.sh)
sudo apt update && sudo apt install -y rtl-sdr nodejs npm
# Install
git clone https://github.com/dj3ky/pagermonitor.git ~/pagermonitor
cd ~/pagermonitor && bash install.sh
# Configure frequency
nano ~/pagermonitor/backend/.env
# Set: RTL_FM_FREQ=173.250M
# Start
sudo systemctl start pagermonitorOpen http://<pi-ip>:3000 Β· Login: admin / see startup log for generated password Β· Change password immediately.
git clone https://github.com/dj3ky/pagermonitor.git
cd pagermonitor
make setup # creates .env from template
nano .env # set your frequency
make start # builds and starts# On the server (no dongle needed)
make start-server
# Open admin panel β SDR Client Key β generate key
# On the Raspberry Pi
cp client/.env.example client/.env
nano client/.env # set SERVER_URL, CLIENT_KEY, RTL_FM_FREQ
make start-client| Item | Notes |
|---|---|
| RTL-SDR dongle | RTL2832U chipset, ~β¬25. RTL-SDR Blog V3/V4 recommended |
| Antenna | Dipole or discone for best reception |
| Raspberry Pi | Pi 4 (2GB+) for single-device. Pi 3B+ or Zero 2W for client-only |
No RTL-SDR dongle? You can still run PagerMonitor in server mode (DISABLE_SDR=true) and forward messages from a remote Pi client.
ββββββββββββββββββββ Single device βββββββββββββββββββββββββ
β β
β RTL-SDR dongle β
β β β
β rtl_fm (frequency tuner) β
β β raw PCM audio β
β multimon-ng (POCSAG/FLEX decoder) β
β β decoded text β
β Node.js server ββββ SQLite database β
β β β
β React frontend βββββ WebSocket β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββ Distributed βββββββββββββββββββββββββββββββββββββββββββββ
β β
β Raspberry Pi Server (VM / NAS / PC) β
β ββββββββββββββββ ββββββββββββββββββββββββββββ β
β β rtl_fm β β Node.js + SQLite β β
β β multimon-ng βββββββΊβ React web UI β β
β β pm-client β HTTP β Admin panel β β
β ββββββββββββββββ POST β Notifications β β
β ββββββββββββββββββββββββββββ β
β β² browsers β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Multiple RPi clients can connect to the same server.
| Layer | Technology |
|---|---|
| SDR | rtl_fm + multimon-ng |
| Backend | Node.js 20, Express, better-sqlite3, ws |
| Database | SQLite with FTS5 full-text search |
| Frontend | React 18, Vite, Leaflet (maps) |
| Auth | bcrypt, Bearer token sessions |
| Notifications | node-fetch, nodemailer, web-push (VAPID) |
| AI geocoding | Groq API Β· OpenAI API Β· Ollama (local) β all optional |
| Process | systemd (native) or Docker Compose |
No external services required by default. Everything runs locally. AI geocoding is optional β enable it for better address extraction without any impact on the core pipeline.
Check CHANGELOG.md first to see what changed.
cd ~/pagermonitor
bash update.shThis pulls the latest code, upgrades system packages, upgrades multimon-ng if a newer version is available, rebuilds the frontend, and restarts the service automatically.
Or use the admin panel β Admin β System β Update shows the current vs latest commit and has an Update Now button that streams live progress and reloads the page when done.
The RPi client has no web UI β update it via SSH:
cd ~/pagermonitor && bash update.sh
cd ~/pagermonitor
git pull
make update # pulls, rebuilds, restarts in one commandAdmin β System β Update shows the current and latest GitHub commit. Or:
curl -s http://localhost:3000/health | grep versionAfter major version bumps (e.g.
2.x β 3.0) always read the CHANGELOG β there may be a manual migration step. Minor and patch versions are always safe to update without any extra steps.
# Health check β use with Uptime Kuma, Zabbix, etc.
curl http://localhost:3000/health
# Response
{
"ok": true,
"status": "healthy",
"uptime": { "human": "2d 4h 13m" },
"database": { "messages": 4821, "today": 47 },
"sdr": { "running": true }
}Full REST API + WebSocket documented in INSTALL.md.
| Document | Contents |
|---|---|
| INSTALL.md | Full installation guide, configuration reference, API docs, troubleshooting |
| DOCKER.md | Docker setup, profiles, Makefile commands, environment variables |
| CHANGELOG.md | Version history β what changed in each release |
| client/.env.example | RPi client configuration with multi-dongle examples |
| .env.example | Server configuration β all variables documented |
MIT β free to use, modify, and distribute.
Built for the amateur radio and emergency services monitoring community.
If this project is useful to you, consider giving it a β
Made with β€οΈ in Slovenia Β· RTL-SDR + Node.js + React