Pomerium reverse proxy

Before you start
  • If you use a self-hosted proxy, PostHog can't help troubleshoot. Use our managed reverse proxy if you want support.
  • Use domains matching your PostHog region: us.i.posthog.com for US, eu.i.posthog.com for EU.
  • Don't use obvious path names like /analytics, /tracking, /telemetry, or /posthog. Blockers will catch them. Use something unique to your app instead.

This guide shows you how to use Pomerium as a reverse proxy for PostHog.

How it works

Pomerium is an identity-aware proxy that sits between your users and backend services. For PostHog, you configure Pomerium to route requests from your domain to PostHog's servers with public access enabled (no authentication required).

Here's the request flow:

  1. User triggers an event in your app
  2. Request goes to your Pomerium proxy (e.g., e.yourdomain.com)
  3. Pomerium matches the request to a route and checks the policy
  4. With public access enabled, Pomerium forwards the request to PostHog
  5. PostHog's response is returned to the user under your domain

Why public access? Unlike typical Pomerium use cases where you want to authenticate users, PostHog analytics should work for all visitors including anonymous users. You'll create a policy that allows unauthenticated access.

Prerequisites

Choose your setup option

Both options accomplish the same goal. Choose based on your Pomerium deployment:

  • Pomerium Zero: The managed service with a web-based UI. Use this if you want the simplest setup without managing infrastructure.
  • Pomerium Core: The self-hosted version configured with YAML files. Use this if you're already running Pomerium Core or prefer configuration as code.

Option 1: Pomerium Zero

Pomerium Zero uses a web-based console for configuration. You'll create a policy and two routes.

  1. Create a public access policy

    In the Pomerium Zero console, create a new policy with public access enabled. This allows unauthenticated requests to pass through, which is required for PostHog analytics to work for all visitors.

    Configure these settings:

    SettingValue
    Nameallow-all or similar
    Policy EnforcementOptional
    Public AccessEnabled in overrides section

    See Pomerium's policy documentation for detailed instructions.

  2. Create the PostHog routes

    Create two routes in the Pomerium Zero console, both using the public access policy you created:

    RouteFromToPath prefix
    PostHoghttps://e.yourdomain.comhttps://us.i.posthog.comnone
    PostHog Assetshttps://e.yourdomain.comhttps://us-assets.i.posthog.com/static

    Replace e.yourdomain.com with your subdomain. Replace us with eu for EU region.

    The assets route needs the /static prefix so it only matches requests for PostHog's JavaScript SDK and other static files. All other requests go to the main route.

    See Pomerium's routing documentation for detailed instructions.

  3. Update your PostHog SDK

    In your application code, update your PostHog initialization:

    posthog.init('<ph_project_api_key>', {
    api_host: 'https://e.yourdomain.com',
    ui_host: 'https://us.posthog.com'
    })

    Replace e.yourdomain.com with your actual subdomain.

  4. Verify your setup

    Checkpoint

    Confirm events are flowing through your proxy:

    1. Open your browser's developer tools and go to the Network tab
    2. Trigger an event in your app
    3. Look for requests to your subdomain (e.g., e.yourdomain.com)
    4. Verify the response status is 200 OK and you're not prompted to authenticate
    5. Check the PostHog app to confirm events appear

    If you see errors, check troubleshooting below.

Option 2: Pomerium Core

  1. Add routes to your config

    Add these routes to your Pomerium config.yaml:

    routes:
    - from: https://e.yourdomain.com
    to: https://us.i.posthog.com
    policy:
    - allow:
    or:
    - public: true
    name: "PostHog"
    - from: https://e.yourdomain.com
    to: https://us-assets.i.posthog.com
    prefix: "/static"
    policy:
    - allow:
    or:
    - public: true
    name: "PostHog Assets"

    Replace e.yourdomain.com with your subdomain.

    The public: true policy allows unauthenticated access. The prefix: "/static" ensures the assets route only matches static file requests.

    See Pomerium's routes documentation for more configuration options.

  2. Reload Pomerium

    Apply the configuration changes:

    Terminal
    pomerium-cli config check # Validate your config
    systemctl reload pomerium # Or your preferred restart method
  3. Update your PostHog SDK

    In your application code, update your PostHog initialization:

    posthog.init('<ph_project_api_key>', {
    api_host: 'https://e.yourdomain.com',
    ui_host: 'https://us.posthog.com'
    })

    Replace e.yourdomain.com with your actual subdomain.

  4. Verify your setup

    Checkpoint

    Confirm events are flowing through your proxy:

    1. Test the proxy directly:

      Terminal
      curl -I https://e.yourdomain.com/decide?v=3

      You should see a 200 OK response without authentication prompts.

    2. Open your browser's developer tools and go to the Network tab

    3. Trigger an event in your app

    4. Look for requests to your subdomain

    5. Check the PostHog app to confirm events appear

    If you see errors, check troubleshooting below.

Troubleshooting

Authentication prompts appearing

If users are prompted to log in when accessing your proxy, verify your policy has public access enabled and is applied to both routes. See Pomerium's authorization documentation for policy configuration details.

404 errors on static assets

If the PostHog SDK fails to load, verify the assets route has the /static prefix configured and points to the correct domain (us-assets.i.posthog.com or eu-assets.i.posthog.com).

DNS not resolving

If your subdomain doesn't resolve, verify your DNS records point to your Pomerium instance. For Pomerium Zero, check Pomerium's networking documentation to ensure the subdomain is configured correctly.

Community questions

Was this page useful?

Questions about this page? or post a community question.