Tiny Tiny RSS (tt-rss) has been the go-to self-hosted RSS reader for a decade — hundreds of feeds, categories, labels, filters, a mobile-friendly JSON API for Reeder and FeedMe, and a plugin ecosystem for extending almost everything. But tt-rss has a critical architectural split: the web interface (PHP on nginx) and the update daemon (a separate PHP process that continuously fetches feeds) run independently. The web UI can stay perfectly responsive while the update daemon has been dead for 12 hours, leaving you with stale feeds and no visible indication anything is wrong. Vigilmon covers both halves: the web and API layer that serves you articles, and the update daemon that actually fetches them.
What You'll Set Up
- HTTP monitor for the tt-rss web UI (port 80/443)
- HTTP monitor for the JSON API liveness check (
/api/) - HTTP monitor for the authenticated JSON API login
- SSL certificate expiry alerts for HTTPS deployments
- Heartbeat monitor that confirms the tt-rss update daemon is fetching feeds
Prerequisites
- Tiny Tiny RSS deployed and accessible (nginx + PHP-FPM + PostgreSQL is the standard stack)
- A tt-rss admin username and password
- A free Vigilmon account
Step 1: Monitor the tt-rss Web UI
The tt-rss web interface is served by PHP on nginx. A GET / returns the login page containing the text "Tiny Tiny RSS" — a 200 response confirms nginx is proxying requests, PHP-FPM is processing them, and the PostgreSQL connection used for session handling is healthy.
- Log in to vigilmon.online and click Add Monitor.
- Set Type to
HTTP / HTTPS. - Enter your tt-rss URL:
https://rss.yourdomain.com(orhttp://your-server:80). - Set Check interval to
1 minute. - Set Expected HTTP status to
200. - Under Response body check, enter
Tiny Tiny RSSto confirm the login page loaded rather than an nginx error page. - Click Save.
The body keyword check is important here — nginx can return 200 with a PHP error page or a "502 Bad Gateway" page styled to look like a landing page. Tiny Tiny RSS in the body confirms the PHP application is actually running.
Step 2: Monitor the JSON API Liveness
The tt-rss JSON API at /api/ accepts POST requests and returns structured JSON responses. A lightweight liveness check uses the isLoggedIn operation, which requires no credentials and returns a 200 with JSON containing "status":0 and "status":false in the content — confirming the PHP API endpoint is processing requests and the database is connected.
- Click Add Monitor in Vigilmon.
- Set Type to
HTTP / HTTPS. - Enter the API URL:
https://rss.yourdomain.com/api/ - Set HTTP method to
POST. - Set Request body to:
{"op": "isLoggedIn", "sid": ""} - Set Request headers:
Content-Type: application/json - Set Expected HTTP status to
200. - Under Response body check, enter
"status":0to verify the API returned a valid JSON response. - Set Check interval to
2 minutes. - Click Save.
Verify manually:
curl -s -X POST https://rss.yourdomain.com/api/ \
-H "Content-Type: application/json" \
-d '{"op": "isLoggedIn", "sid": ""}' | python3 -m json.tool
# {
# "status": 0,
# "content": {"status": false}
# }
A PHP error, database failure, or misconfigured CORS header will either return a non-200 status or a response body that doesn't contain "status":0.
Step 3: Monitor the Authenticated Login API
The login API operation confirms full end-to-end authentication: PHP processes the request, PostgreSQL validates the credentials, and tt-rss returns a session ID. This is the deepest database integration check available without a custom endpoint.
- Click Add Monitor in Vigilmon.
- Set Type to
HTTP / HTTPS. - Enter the API URL:
https://rss.yourdomain.com/api/ - Set HTTP method to
POST. - Set Request body to:
{"op": "login", "user": "admin", "password": "your-password"} - Set Request headers:
Content-Type: application/json - Set Expected HTTP status to
200. - Under Response body check, enter
session_idto confirm a session token was returned. - Set Check interval to
5 minutes. - Click Save.
Use a dedicated monitoring account in tt-rss rather than the admin account — create a read-only user via Preferences → Users (requires admin role) to avoid exposing the admin password in a Vigilmon monitor.
Step 4: SSL Certificate Alerts for HTTPS Deployments
tt-rss is almost always deployed with HTTPS — the JSON API is used by mobile apps that may fail silently on certificate errors. The nginx reverse proxy manages TLS; a certificate expiry breaks all API clients immediately.
- Open the HTTPS monitor for your tt-rss domain (created in Step 1).
- Enable Monitor SSL certificate under the SSL settings.
- Set Alert when certificate expires in less than
21 days. - Click Save.
For nginx + certbot, verify auto-renewal:
# Check renewal timer
systemctl status certbot.timer
# Test renewal
sudo certbot renew --dry-run
# Check current certificate expiry
echo | openssl s_client -connect rss.yourdomain.com:443 -servername rss.yourdomain.com 2>/dev/null \
| openssl x509 -noout -dates
Step 5: Heartbeat Monitoring for the Update Daemon
The tt-rss update daemon is a separate PHP process (update.php --daemon) that runs continuously in the background, fetching and updating feeds on a per-feed schedule. If it dies — due to a PHP fatal error, memory exhaustion, a stuck feed, or a server restart that didn't restart the daemon — your feeds stop updating silently.
The update daemon doesn't expose a health endpoint, so the most reliable approach is a cron job that queries the PostgreSQL database directly for recent feed update activity and pings Vigilmon only when updates have occurred recently.
- In Vigilmon, click Add Monitor → Cron Heartbeat.
- Set Expected ping interval to
30 minutes. - Copy the heartbeat URL (e.g.
https://vigilmon.online/heartbeat/mno345).
Set up a cron job on the tt-rss server:
#!/bin/bash
# /etc/cron.d/ttrss-daemon-heartbeat
VIGILMON_HEARTBEAT="https://vigilmon.online/heartbeat/mno345"
TTRSS_DB="ttrss"
TTRSS_DB_USER="ttrss"
DB_HOST="localhost"
THRESHOLD_MINUTES=30
# Check if any feed has been updated in the last THRESHOLD_MINUTES minutes
RECENT_UPDATES=$(psql -h "${DB_HOST}" -U "${TTRSS_DB_USER}" -d "${TTRSS_DB}" -tAc \
"SELECT COUNT(*) FROM ttrss_feeds WHERE last_updated > NOW() - INTERVAL '${THRESHOLD_MINUTES} minutes';")
if [ "${RECENT_UPDATES}" -gt 0 ]; then
curl -s "${VIGILMON_HEARTBEAT}" > /dev/null
else
echo "No feed updates in the last ${THRESHOLD_MINUTES} minutes — update daemon may be down" >&2
exit 1
fi
Add to crontab (run every 30 minutes, matching the Vigilmon interval):
*/30 * * * * ttrss /etc/cron.d/ttrss-daemon-heartbeat >> /var/log/ttrss-heartbeat.log 2>&1
For Docker deployments, run the check inside the container:
docker exec ttrss psql -U ttrss -d ttrss -tAc \
"SELECT COUNT(*) FROM ttrss_feeds WHERE last_updated > NOW() - INTERVAL '30 minutes';"
You can also check the update daemon process directly as a secondary verification:
# Check if the update daemon is running
if ! pgrep -f "update.php.*daemon" > /dev/null; then
echo "tt-rss update daemon not found in process list" >&2
# Attempt to restart
sudo systemctl restart ttrss-update-daemon
fi
If the update daemon has been dead for 30 minutes with no feed updates, the heartbeat is not sent and Vigilmon alerts — giving you immediate visibility into the most common tt-rss failure mode.
Step 6: Monitor the Update Daemon as a System Service
If you run the update daemon as a systemd service, add a monitor to confirm the service is active. Create a small status endpoint script:
#!/bin/bash
# /usr/local/bin/ttrss-status-check
if systemctl is-active --quiet ttrss-update-daemon; then
echo "ok"
exit 0
else
echo "daemon-stopped"
exit 1
fi
Then expose it via a lightweight HTTP server or add it to the heartbeat script above. For simpler setups, the database-based feed update check in Step 5 is more reliable than a process check — a running but stuck daemon will still fail the update timestamp query.
Step 7: Configure Alert Channels
- Go to Alert Channels in Vigilmon and add Slack, email, or a webhook.
- Set Consecutive failures before alert to
2on the web UI monitor — PHP-FPM restarts and nginx reloads can cause brief probe failures. - Set Consecutive failures before alert to
1on the JSON API liveness monitor — a failedisLoggedInresponse is always meaningful. - For the heartbeat monitor, set the grace period to
35 minutes(30-minute expected interval plus 5 minutes tolerance).
Summary
| Monitor | Target | What It Catches |
|---|---|---|
| Web UI | https://rss.yourdomain.com/ | nginx/PHP-FPM down, database unreachable |
| API liveness | /api/ with isLoggedIn POST | PHP API broken, database connectivity failure |
| Authenticated login | /api/ with login POST | Authentication failure, PostgreSQL user lookup broken |
| SSL certificate | tt-rss domain | Certificate expiry, renewal failure |
| Update daemon heartbeat | Heartbeat URL | Feed update daemon stopped, stalled, or stuck |
tt-rss's dual-process architecture is its main reliability challenge: the web interface and the update daemon are independent, so one can fail without the other showing any symptoms. The database-based heartbeat in Step 5 is what makes this monitoring setup complete — it's the only check that answers the question you actually care about: are my feeds being updated? Everything else just confirms the front end is alive.