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:

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: isHostedServerAuthMode returns true
  • Explicit opt-out: OPENSEO_TELEMETRY_DISABLED set to "off", "0", "false", or "no"
  • Global DNT: DO_NOT_TRACK set 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 maybeSendSelfHostHeartbeat on 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_state table with SQLite and PostgreSQL variants
  • Opt-out works through OPENSEO_TELEMETRY_DISABLED or DO_NOT_TRACK environment 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →