# Vercel reverse proxy - Docs

**Before you start**

-   If you use a self-hosted proxy, PostHog can't help troubleshoot. Use [our managed reverse proxy](/docs/advanced/proxy/managed-reverse-proxy.md) 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 [Vercel rewrites](https://vercel.com/docs/projects/project-configuration#rewrites) as a reverse proxy for PostHog.

## How it works

Vercel rewrites map incoming request paths to different destinations. When you add PostHog rewrites to `vercel.json`, Vercel fetches content from PostHog's servers and returns it under your domain.

Here's the request flow:

1.  User triggers an event in your app
2.  Request goes to your Vercel domain (e.g., `yourdomain.com/ph`)
3.  Vercel matches the request against your rewrite rules
4.  Vercel fetches the response from PostHog's servers
5.  Vercel returns PostHog's response under your domain

This works because the rewrite happens server-side, so the browser only sees requests to your domain. Ad blockers that filter by domain won't block these requests.

## Using Next.js on Vercel?

If you're using Next.js, you can configure rewrites in either `vercel.json` or `next.config.js`. Both work identically on Vercel.

-   Use this guide (`vercel.json`) if you want all Vercel-specific settings in one file
-   Use [Next.js rewrites](/docs/advanced/proxy/nextjs.md) if you prefer keeping config in `next.config.js`

Don't use both at the same time as they may conflict.

## Prerequisites

This guide requires a project deployed on Vercel.

## Setup

1.  1

    ## Create vercel.json

    In your project root, create or update `vercel.json`:

    PostHog AI

    ### US

    ```json
    {
      "rewrites": [
        {
          "source": "/ph/static/:path(.*)",
          "destination": "https://us-assets.i.posthog.com/static/:path"
        },
        {
          "source": "/ph/array/:path(.*)",
          "destination": "https://us-assets.i.posthog.com/array/:path"
        },
        {
          "source": "/ph/:path(.*)",
          "destination": "https://us.i.posthog.com/:path"
        }
      ]
    }
    ```

    ### EU

    ```json
    {
      "rewrites": [
        {
          "source": "/ph/static/:path(.*)",
          "destination": "https://eu-assets.i.posthog.com/static/:path"
        },
        {
          "source": "/ph/array/:path(.*)",
          "destination": "https://eu-assets.i.posthog.com/array/:path"
        },
        {
          "source": "/ph/:path(.*)",
          "destination": "https://eu.i.posthog.com/:path"
        }
      ]
    }
    ```

    Here's what each part does:

    -   The first rewrite routes `/ph/static/*` requests to PostHog's asset server. This serves the JavaScript SDK and other static files.
    -   The second rewrite routes `/ph/array/*` requests to PostHog's asset server. This serves the remote config (e.g., `/array/<token>/config.js`) with proper `cache-control` headers. Without this rule, remote config requests fall through to the main API, which strips cache headers and can cause browsers to use stale SDK config.
    -   The third rewrite routes all other `/ph/*` requests to PostHog's main API. This handles event capture, Feature flags, and session recordings.
    -   The `:path(.*)` syntax captures everything after the matched prefix and passes it to the destination URL.

    The specific path rewrites must come before the catch-all. Vercel evaluates rewrites in order, so if the catch-all rule came first, it would match `/ph/static/*` and `/ph/array/*` before the path-specific rules.

    See [Vercel's rewrites documentation](https://vercel.com/docs/projects/project-configuration#rewrites) for more details.

2.  2

    ## Update your PostHog SDK

    In your application code, update your PostHog initialization:

    PostHog AI

    ### US

    ```javascript
    posthog.init('<ph_project_token>', {
      api_host: '/ph',
      ui_host: 'https://us.posthog.com'
    })
    ```

    ### EU

    ```javascript
    posthog.init('<ph_project_token>', {
      api_host: '/ph',
      ui_host: 'https://eu.posthog.com'
    })
    ```

    The `api_host` tells the SDK where to send events. Using a relative path ensures requests go to your domain. The `ui_host` must point to PostHog's actual domain so features like the toolbar link correctly.

3.  3

    ## Deploy to Vercel

    Commit and push your changes. The rewrites become active once deployed.

    You can verify the configuration in your Vercel dashboard under **Project Settings** → **Functions** → **Rewrites**.

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.  Navigate to your site or trigger an event
    3.  Look for requests to your domain with your proxy path (e.g., `yourdomain.com/ph`)
    4.  Verify the response status is `200 OK`
    5.  Check the [PostHog app](https://app.posthog.com) to confirm events appear in your activity feed

    If you see errors, check [troubleshooting](#troubleshooting) below.

## Troubleshooting

### Rewrites not working

If requests to your proxy path return 404:

1.  Verify `vercel.json` is in your project root (same level as `package.json`)
2.  Check the file is valid JSON with no syntax errors
3.  Ensure the rewrites are in the correct order: static assets first, then array, then catch-all
4.  Check the Vercel dashboard to confirm the config was deployed

### 401 errors after deployment

If events work locally but return 401 errors on Vercel, this usually indicates a region mismatch. Verify your rewrite destinations match your PostHog project's region:

-   US region: `us.i.posthog.com` and `us-assets.i.posthog.com`
-   EU region: `eu.i.posthog.com` and `eu-assets.i.posthog.com`

### Conflicts with Next.js rewrites

If you're using both `vercel.json` rewrites and Next.js `next.config.js` rewrites, they may conflict. Choose one method:

-   Use `vercel.json` for all Vercel-specific config
-   Or use `next.config.js` for framework-level config

Remove the PostHog rewrites from whichever file you're not using.

### Rewrite pattern not matching

If your rewrites aren't matching correctly, ensure you're using Vercel's named parameter syntax:

PostHog AI

### US

```json
{
  "source": "/ph/:path(.*)",
  "destination": "https://us.i.posthog.com/:path"
}
```

### EU

```json
{
  "source": "/ph/:path(.*)",
  "destination": "https://eu.i.posthog.com/:path"
}
```

The `:path(.*)` captures the path and `:path` in the destination references it. Don't use `(.*)` without the named parameter.

### Community questions

Ask a question

### Was this page useful?

HelpfulCould be better