PostHog Troubleshooting & Debugging | OpsBlu Docs

PostHog Troubleshooting & Debugging

Diagnose and fix common PostHog tracking issues. Covers script loading failures, missing events, data discrepancies, integration conflicts, and...

Overview

Even the best implementations run into issues. This guide covers the most common PostHog problems, how to diagnose them, and how to fix them quickly. Whether events aren't showing up, session recordings aren't capturing, or feature flags aren't working as expected, you'll find answers here.

The key to debugging PostHog is understanding the data flow: SDK → ingestion endpoint → processing → storage → dashboard. Problems can occur at any stage, and knowing where to look saves time.

Events Not Showing Up

This is the most common issue. Events are being tracked in code but don't appear in PostHog.

Diagnosis

1. Check browser console:

// Enable debug mode
posthog.init('YOUR_API_KEY', {
  api_host: 'https://app.posthog.com',
  debug: true  // Verbose logging
});

// You should see:
// [PostHog] Event captured: your_event_name
// [PostHog] Sending batch of N events

2. Check network requests:

  • Open browser DevTools → Network tab
  • Filter for posthog or your self-hosted domain
  • Look for POST requests to /e/ or /batch/
  • Inspect request payload and response

Expected successful response:

{
  "status": 1
}

3. Check PostHog activity:

  • Go to Activity in PostHog dashboard
  • Filter by your event name or distinct_id
  • Events should appear within 10-30 seconds

Common Causes & Solutions

API key incorrect:

// Wrong
posthog.init('ph_test_WRONG_KEY', { ... });

// Correct - get from Settings > Project
posthog.init('phc_YOUR_ACTUAL_PROJECT_API_KEY', { ... });

Wrong API host:

// Wrong for Cloud users
posthog.init('YOUR_API_KEY', {
  api_host: 'https://posthog.com'  // This is the marketing site!
});

// Correct for Cloud
posthog.init('YOUR_API_KEY', {
  api_host: 'https://app.posthog.com'  // or 'https://eu.posthog.com' for EU
});

// Correct for self-hosted
posthog.init('YOUR_API_KEY', {
  api_host: 'https://posthog.yourdomain.com'
});

Ad blockers: Ad blockers often block analytics tools. Test in incognito or with ad blocker disabled.

Solution - Use a reverse proxy:

// Route PostHog through your domain to avoid blockers
posthog.init('YOUR_API_KEY', {
  api_host: 'https://yourdomain.com',  // Your domain
  ui_host: 'https://app.posthog.com'   // PostHog dashboard
});

// Set up reverse proxy (Nginx example)
// location /posthog/ {
//   proxy_pass https://app.posthog.com/;
// }

Content Security Policy (CSP) blocking:

<!-- Add PostHog domains to CSP -->
<meta http-equiv="Content-Security-Policy"
      content="script-src 'self' https://app.posthog.com;
               connect-src 'self' https://app.posthog.com;">

SDK not initialized before tracking:

// Wrong - tracking before init completes
posthog.init('YOUR_API_KEY');
posthog.capture('event');  // Might be lost

// Correct - wait for init
posthog.init('YOUR_API_KEY', {
  loaded: (posthog) => {
    posthog.capture('app_loaded');
  }
});

CORS issues (self-hosted):

// Self-hosted PostHog needs CORS configured
// In your PostHog config:
// ALLOWED_ORIGIN_HOSTS: ["yourdomain.com"]

Events batched/delayed: PostHog batches events by default (every 10 seconds or 20 events). This is normal.

// Force immediate send for testing
posthog.capture('test_event');
posthog._send_request({});  // Internal method, use cautiously

// Better: reduce batch interval
posthog.init('YOUR_API_KEY', {
  loaded: (posthog) => {
    posthog.set_config({
      batch_size: 1,  // Send immediately
      batch_interval: 1000  // 1 second
    });
  }
});

Session Recordings Not Working

Session recordings require proper setup and can fail silently if misconfigured.

Diagnosis

1. Check if recording is enabled:

posthog.init('YOUR_API_KEY', {
  api_host: 'https://app.posthog.com',
  autocapture: true,
  session_recording: {
    enabled: true  // Must be true
  }
});

// Or check programmatically
console.log(posthog.sessionRecordingStarted());  // Should return true

2. Check browser console for errors:

[PostHog] Session recording failed to start
[PostHog] rrweb not loaded

3. Check network requests:

  • Look for requests to /s/ (session recording endpoint)
  • Recordings upload in chunks as user interacts

4. Check PostHog dashboard:

  • Go to Session Recordings
  • Filter by your distinct_id
  • Recordings appear within minutes

Common Causes & Solutions

Recording disabled in project settings:

  1. Go to PostHog Settings > Project
  2. Check "Enable session recordings" is ON
  3. Set minimum duration if needed

Third-party iframes blocking: Session recordings can't capture content inside third-party iframes (e.g., YouTube, Twitter embeds). This is a browser security limitation.

Performance issues:

// Reduce recording quality for performance
posthog.init('YOUR_API_KEY', {
  session_recording: {
    enabled: true,
    quality: 'low',  // 'low', 'medium', 'high'
    sampling_ratio: 0.5  // Record only 50% of sessions
  }
});

Mask sensitive data:

posthog.init('YOUR_API_KEY', {
  session_recording: {
    enabled: true,
    maskAllInputs: true,  // Mask all form inputs
    maskTextSelector: '.sensitive-data',  // Mask specific elements
    blockClass: 'ph-no-record',  // Don't record these elements
    blockSelector: '[data-private]'  // Don't record by selector
  }
});

Single Page App (SPA) navigation:

// For React, Vue, Angular, etc.
posthog.init('YOUR_API_KEY', {
  capture_pageview: true,  // Track route changes
  session_recording: {
    enabled: true,
    recordCrossOriginIframes: false  // Usually need to be false
  }
});

// Manually trigger pageview on route change if needed
router.afterEach((to, from) => {
  posthog.capture('$pageview');
});

Canvas/WebGL not captured: PostHog can't record canvas or WebGL content. This is a limitation of session replay technology.

Feature Flags Not Working

Feature flags are powerful but can be tricky if user identification or flag evaluation isn't set up correctly.

Diagnosis

1. Check flag exists and is enabled:

  • Go to Feature Flags in PostHog
  • Ensure flag is active (not draft)
  • Check rollout percentage or filters

2. Check flag evaluation:

// Check if flag is enabled for current user
const isEnabled = posthog.isFeatureEnabled('new-dashboard');
console.log('Flag enabled:', isEnabled);

// Check all flags
console.log('All flags:', posthog.getAllFlags());

// Check feature flag value (for multivariate flags)
const variant = posthog.getFeatureFlag('experiment-checkout');
console.log('Variant:', variant);

3. Enable debug mode:

posthog.init('YOUR_API_KEY', {
  api_host: 'https://app.posthog.com',
  debug: true,  // Logs flag evaluations
  advanced_disable_decide: false  // Ensure /decide endpoint is called
});

Common Causes & Solutions

Flag not loaded yet:

// Wrong - checking before flags load
posthog.init('YOUR_API_KEY');
if (posthog.isFeatureEnabled('new-dashboard')) {
  // Might evaluate incorrectly
}

// Correct - wait for flags to load
posthog.onFeatureFlags(() => {
  if (posthog.isFeatureEnabled('new-dashboard')) {
    // Now reliable
  }
});

// Or use callback
posthog.init('YOUR_API_KEY', {
  loaded: (posthog) => {
    posthog.onFeatureFlags(() => {
      // Flags ready
    });
  }
});

User not identified:

// Feature flags rely on distinct_id
// Anonymous users get flags, but targeting won't work until identified

// Identify user first
posthog.identify('user_123', {
  email: 'user@example.com',
  plan: 'pro'
});

// Then check flags
posthog.onFeatureFlags(() => {
  const hasFeature = posthog.isFeatureEnabled('pro-only-feature');
});

Flag targeting doesn't match user:

// If flag is targeted to users with plan: "enterprise"
// But user has plan: "pro", flag won't be enabled

// Check user properties
console.log(posthog.persistence.properties());

// Ensure properties match flag targeting
posthog.setPersonProperties({
  plan: 'enterprise'  // Now flag might be enabled
});

// Force reload flags
posthog.reloadFeatureFlags();

Local evaluation disabled: For high-performance scenarios, enable local flag evaluation:

posthog.init('YOUR_API_KEY', {
  api_host: 'https://app.posthog.com',
  advanced_disable_feature_flags: false,
  advanced_disable_decide: false,  // Needed for flag evaluation
  persistence: 'localStorage'  // or 'cookie'
});

Server-side flags:

// Node.js
const { PostHog } = require('posthog-node');
const posthog = new PostHog('YOUR_API_KEY');

// Check flag server-side
const isEnabled = await posthog.isFeatureEnabled(
  'new-api-endpoint',
  'user_123'
);

if (isEnabled) {
  // Use new endpoint
}

// Remember to shutdown
await posthog.shutdown();

User Identity Issues

Problems with user identification can break analytics, recordings, and feature flags.

Diagnosis

Check current distinct_id:

console.log('Distinct ID:', posthog.get_distinct_id());
console.log('User properties:', posthog.persistence.properties());

Check user timeline in PostHog:

  1. Go to Persons & Groups > Persons
  2. Search for your user
  3. View their event timeline
  4. Check for duplicate profiles

Common Causes & Solutions

Identifying too late:

// Wrong - user does stuff before being identified
posthog.capture('page_viewed');
posthog.capture('button_clicked');
// ... later ...
posthog.identify('user_123');  // Previous events orphaned

// Correct - identify immediately on login/signup
function handleLogin(userId) {
  posthog.identify(userId);
  // Now all events are tied to user_123
}

Identifying too often:

// Wrong - identifying on every page load
useEffect(() => {
  posthog.identify('user_123');  // Don't do this!
}, []);

// Correct - identify only on login/signup
function handleLogin(userId) {
  posthog.identify(userId);
  // Store in localStorage or session to avoid re-identifying
}

Not resetting on logout:

// Wrong - same distinct_id persists after logout
function handleLogout() {
  // Just log out
}

// Correct - reset PostHog on logout
function handleLogout() {
  posthog.reset();  // Clears distinct_id and generates new anonymous ID
  // Complete logout...
}

Multiple devices/browsers: PostHog merges user profiles when you identify with the same user ID from different devices. This is expected behavior.

Server-side vs client-side distinct_id mismatch:

// Ensure same distinct_id on client and server
// Client:
posthog.identify('user_123');

// Server (Node.js):
posthog.capture({
  distinctId: 'user_123',  // Must match client
  event: 'subscription_created'
});

Cross-Domain Tracking Issues

Tracking users across multiple domains requires special configuration.

Diagnosis

Check if user has same distinct_id on both domains:

  1. Visit domain A, check: posthog.get_distinct_id()
  2. Navigate to domain B, check: posthog.get_distinct_id()
  3. Should be the same

Check cookies: PostHog stores distinct_id in cookies. Cross-domain requires cookie sharing.

Solution

Enable cross-domain tracking:

// Domain A (main site)
posthog.init('YOUR_API_KEY', {
  api_host: 'https://app.posthog.com',
  cross_subdomain_cookie: true,  // Share cookies across subdomains
  persistence: 'cookie'
});

// Domain B (different domain)
posthog.init('YOUR_API_KEY', {
  api_host: 'https://app.posthog.com',
  cross_subdomain_cookie: true
});

For completely different domains: You need to pass distinct_id via URL parameter:

// Domain A - add distinct_id to link
const link = `https://domainb.com?ph_distinct_id=${posthog.get_distinct_id()}`;

// Domain B - read from URL and identify
const urlParams = new URLSearchParams(window.location.search);
const distinctId = urlParams.get('ph_distinct_id');
if (distinctId) {
  posthog.identify(distinctId);
}

Performance Issues

PostHog should have minimal performance impact, but misconfigurations can cause problems.

Diagnosis

Check bundle size:

# PostHog JS SDK is ~30KB gzipped
# If much larger, you might be bundling unnecessarily

npm run build -- --analyze  # For webpack bundle analyzer

Check network waterfall:

  • PostHog should load asynchronously
  • Events should batch, not send individually
  • Session recordings upload incrementally

Solutions

Load PostHog asynchronously:

<script>
  !function(t,e){...}(window,document);  // Async snippet
  posthog.init('YOUR_API_KEY');
</script>

Reduce autocapture overhead:

posthog.init('YOUR_API_KEY', {
  autocapture: {
    css_selector_blacklist: ['.no-track', '#ignore-this']  // Reduce DOM scanning
  }
});

Optimize session recordings:

posthog.init('YOUR_API_KEY', {
  session_recording: {
    quality: 'low',  // Smaller payload
    sampling_ratio: 0.25,  // Record only 25% of sessions
    minimumDuration: 5000  // Don't record sessions < 5 seconds
  }
});

Batch events efficiently:

posthog.init('YOUR_API_KEY', {
  loaded: (posthog) => {
    posthog.set_config({
      batch_size: 50,  // Send in batches of 50 (default: 20)
      batch_interval: 20000  // Every 20 seconds (default: 10000)
    });
  }
});

Data Quality Issues

Sometimes data arrives but looks wrong.

Missing or Incorrect Properties

Check event payload in network tab:

{
  "event": "button_clicked",
  "properties": {
    "button_name": null,  // Should have value
    "$current_url": "https://example.com/page"
  }
}

Fix:

// Ensure properties are set before capture
const buttonName = button.getAttribute('data-name') || 'unknown';
posthog.capture('button_clicked', {
  button_name: buttonName  // Now has value
});

Timezone Issues

PostHog stores all timestamps in UTC. If times look wrong, check your dashboard timezone:

  1. Go to Settings > Project Settings
  2. Set Display Timezone
  3. All charts now show in your timezone

Duplicate Events

Cause: Multiple SDK initializations or event tracking in effect hooks

Fix:

// React - initialize only once
useEffect(() => {
  if (!window.posthogInitialized) {
    posthog.init('YOUR_API_KEY');
    window.posthogInitialized = true;
  }
}, []);  // Empty dependency array

// Don't track in render
function Component() {
  posthog.capture('rendered');  // Called on every render

  useEffect(() => {
    posthog.capture('mounted');  // Called once on mount
  }, []);
}

Self-Hosted Issues

Additional issues specific to self-hosted PostHog.

Events Not Ingesting

Check PostHog is running:

docker ps  # All services should be up

# Check logs
docker logs posthog-web
docker logs posthog-worker
docker logs posthog-clickhouse

Check ingestion endpoint:

curl -X POST https://posthog.yourdomain.com/capture/ \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "YOUR_API_KEY",
    "event": "test_event",
    "distinct_id": "test_user"
  }'

# Should return: {"status":1}

Check ClickHouse:

# Exec into ClickHouse container
docker exec -it posthog-clickhouse clickhouse-client

# Query events table
SELECT event, timestamp FROM events ORDER BY timestamp DESC LIMIT 10;

High Memory Usage

Redis memory:

docker stats posthog-redis

# If high, increase maxmemory in Redis config

ClickHouse memory:

docker stats posthog-clickhouse

# Adjust settings in clickhouse-config.xml

Upgrade Issues

Before upgrading:

# Backup database
docker exec posthog-db pg_dump -U posthog posthog > backup.sql

# Check release notes
# https://github.com/PostHog/posthog/releases

After upgrade:

# Run migrations
docker exec posthog-web python manage.py migrate

Getting Help

Self-Diagnosis Checklist

Before asking for help, check:

  • API key is correct
  • API host matches your deployment (Cloud vs self-hosted)
  • Browser console shows no errors
  • Network requests to PostHog succeed (200 responses)
  • Events appear in PostHog Activity within 30 seconds
  • Ad blockers are disabled or bypassed
  • SDK version is up to date

Debug Information to Share

When asking for help, include:

  • PostHog SDK version
  • Browser/device/OS
  • Steps to reproduce
  • Console logs (with debug: true)
  • Network request/response (from DevTools)
  • PostHog project API key (safe to share)

Resources

Enable Support Access

For Cloud users, you can grant PostHog support access to debug issues:

  1. Go to Settings > Project
  2. Under Support, toggle "Allow PostHog team to access this project"
  3. Share project name with support team

This lets PostHog engineers investigate directly, speeding up resolution.


Remember: Most PostHog issues come down to incorrect API keys, wrong API hosts, or ad blockers. Start there, and you'll solve 80% of problems in minutes.