OpenSEO Self-Hosting Monitoring and Telemetry: Complete Implementation Guide
OpenSEO provides a lightweight, privacy-preserving telemetry system for self-hosted deployments that reports daily health and usage metrics to PostHog, with automatic opt-out support and no personal data collection.
OpenSEO monitors self-hosted installations through an automated heartbeat system designed to give maintainers visibility into deployment health without compromising user privacy. This telemetry implementation runs exclusively in production environments and can be completely disabled through environment variables. The system tracks installation metrics, database backends, and feature usage while strictly avoiding any personal data collection.
How the OpenSEO Telemetry System Works
The core telemetry engine lives in src/server/lib/self-host-telemetry.ts. Every incoming request triggers maybeSendSelfHostHeartbeat, which manages the scheduling and transmission of heartbeat events.
The heartbeat follows an adaptive schedule:
- First 2 hours after installation: Every 5 minutes to capture onboarding behavior
- After initial period: Once per day for ongoing health monitoring
This approach ensures install-time issues are detected quickly while minimizing long-term network overhead.
Telemetry Payload: What OpenSEO Reports
Each heartbeat event (self_host.heartbeat) transmits a structured payload to PostHog at https://us.i.posthog.com with key phc_xaXj4vE4LikxfvR7q6EHemAYNBSZW4hQkqor7fpf8aGT:
| Property | Purpose |
|---|---|
installId |
Random UUID uniquely identifying the installation |
installedAt |
Timestamp of first heartbeat (installation time) |
lastHeartbeatAt |
Timestamp of previous successful heartbeat |
version / prevVersion |
Current and previous OpenSEO versions |
firstRun |
Boolean flag for initial heartbeat |
minutesSinceInstall |
Time elapsed since installation (optional) |
deployTarget |
"docker" for local/no-auth, "cloudflare" otherwise |
dbBackend |
"d1" or "postgres" |
userCount, projectCount, siteAuditCount, rankTrackingKeywordCount, savedKeywordCount |
Core entity counts |
gscConnected |
Google Search Console connection status |
samChatUsed |
SAM chat tool usage flag |
mcpToolCalls |
MCP tool-call counter |
setupIssues |
Compact list of unhealthy setup checks |
$process_person_profile |
Always false — no personal profiling |
The $process_person_profile: false flag ensures PostHog processes this as anonymous event data only.
Telemetry State Storage
OpenSEO persists telemetry metadata in a dedicated table defined in two schema files:
- SQLite:
src/db/telemetry.schema.ts - PostgreSQL:
src/db/pg/telemetry.schema.ts
The telemetry_state table stores:
-- Core fields (SQLite variant)
install_id TEXT PRIMARY KEY,
installed_at INTEGER,
last_heartbeat_at INTEGER,
version TEXT,
prev_version TEXT,
mcp_tool_calls INTEGER DEFAULT 0
This minimal schema ensures the heartbeat can resume correctly after restarts without leaking operational data.
Disabling OpenSEO Telemetry
Telemetry automatically disables when any of these conditions are met (checked by telemetryIsDisabled in src/server/lib/self-host-telemetry.ts):
- Hosted mode:
isHostedServerAuthModereturns true - Explicit opt-out:
OPENSEO_TELEMETRY_DISABLEDset to"off","0","false", or"no" - Global DNT:
DO_NOT_TRACKset to any truthy opt-out value
Environment variable configuration:
# Disable via OpenSEO-specific variable
OPENSEO_TELEMETRY_DISABLED=off
# Or use the global Do Not Track standard
DO_NOT_TRACK=true
The src/shared/selfhost-checks.ts module parses these values consistently across the codebase.
Production-Only Operation
OpenSEO telemetry never runs in development environments. The heartbeat guard in src/server.ts checks:
if (import.meta.env.MODE !== "production") {
// Telemetry skipped — dev, Vitest, or preview build
}
This prevents accidental data pollution from local development and CI pipelines.
MCP Tool-Call Tracking
OpenSEO increments a dedicated counter whenever self-hosted MCP tools are invoked. Call incrementSelfHostMcpToolCallCount from any MCP tool handler:
import { incrementSelfHostMcpToolCallCount } from '@/server/lib/self-host-telemetry';
// Inside your MCP tool implementation
await incrementSelfHostMcpToolCallCount();
This counter is batched into the next heartbeat, providing aggregate usage metrics without per-call network overhead.
Debugging and Manual Control
Force an immediate heartbeat for troubleshooting:
import { maybeSendSelfHostHeartbeat } from '@/server/lib/self-host-telemetry';
// Bypass the 5-minute/24-hour throttle
await maybeSendSelfHostHeartbeat({ skipMemoryThrottle: true });
The pre-flight script at scripts/selfhost-preflight.ts validates telemetry configuration during startup and aborts with a clear message if telemetry is explicitly disabled.
Summary
- OpenSEO telemetry runs via
maybeSendSelfHostHeartbeaton every request, with intelligent throttling to once daily (or every 5 minutes during onboarding) - Payload includes deployment target, database backend, entity counts, and feature flags — never personal data
- Storage uses a minimal
telemetry_statetable with SQLite and PostgreSQL variants - Opt-out works through
OPENSEO_TELEMETRY_DISABLEDorDO_NOT_TRACKenvironment variables - Production-only execution prevents dev environment leakage
- MCP tracking aggregates tool usage through
incrementSelfHostMcpToolCallCount
Frequently Asked Questions
Does OpenSEO telemetry collect any personal or identifiable information?
No. The $process_person_profile property is hardcoded to false, and the payload contains only aggregate counts, installation UUIDs, and configuration flags. No user emails, names, IP addresses, or content data are transmitted.
How do I completely disable telemetry in my self-hosted OpenSEO instance?
Set either OPENSEO_TELEMETRY_DISABLED=off or DO_NOT_TRACK=true in your environment. The src/server/lib/self-host-telemetry.ts module checks these via telemetryIsDisabled and suppresses all heartbeat activity when detected.
Why does OpenSEO send heartbeats every 5 minutes initially?
The accelerated cadence during the first two hours captures onboarding friction and setup issues. After minutesSinceInstall exceeds 120, the throttle relaxes to daily intervals. This is implemented in maybeSendSelfHostHeartbeat using the stored installedAt timestamp.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →