Using node:http as a reverse proxy

Last updated:

|Edit this page|
Warning
  1. The following self-hosted proxy isn't provided by PostHog, so we can't take responsibility for it! If unsure, we recommend using our managed reverse proxy.

  2. If you are using the EU cloud then use eu instead of us in all domains (e.g. us.i.posthog.com -> eu.i.posthog.com).

  3. Avoid using generic or common path names like /analytics, /tracking, /ingest, or /posthog for your reverse proxy. They will most likely be blocked. Instead, use a non-obvious path name or something random and unique to your application that's unlikely to appear in a filter list.

You can use Node's built-in http module to proxy requests to PostHog. This is a simple and lightweight way to proxy requests.

TypeScript
import http from "node:http";
const API_HOST = "eu.i.posthog.com"; // Change to "us.i.posthog.com" for the US region
const ASSET_HOST = "eu-assets.i.posthog.com"; // Change to "us-assets.i.posthog.com" for the US region
// Convert Node.js headers to Fetch API headers
const toHeaders = (headers: Record<string, string[] | undefined>): Headers =>
Object.entries(headers).reduce((acc, [name, values]) => {
values?.forEach((value) => acc.append(name, value));
return acc;
}, new Headers());
// Convert Fetch API headers to Node.js headers
const fromHeaders = (headers: Headers): Record<string, string[] | undefined> =>
[...headers].reduce<Record<string, string[] | undefined>>(
(acc, [name, value]) => {
if (acc[name]) {
acc[name] = [...acc[name], value];
} else {
acc[name] = [value];
}
return acc;
},
{},
);
/**
* Proxies an incoming HTTP request to the appropriate PostHog endpoint.
*/
export default function proxy({ prefix }: { prefix: string }) {
return (
request: http.IncomingMessage,
response: http.ServerResponse,
next: (err?: Error) => void,
): void => {
if (!request.url?.startsWith(prefix)) {
next();
return;
}
const pathname = (request.url ?? "").slice(prefix.length);
const posthogHost = pathname.startsWith("/static/") ? ASSET_HOST : API_HOST;
// Rewrite headers
const headers = toHeaders(request.headersDistinct);
headers.set("host", posthogHost);
if (request.headers.host) {
headers.set("X-Forwarded-Host", request.headers.host);
}
if (request.socket.remoteAddress) {
headers.set("X-Real-IP", request.socket.remoteAddress);
headers.set("X-Forwarded-For", request.socket.remoteAddress);
}
// Remove sensitive or hop-by-hop headers
headers.delete("cookie");
headers.delete("connection"); // https://datatracker.ietf.org/doc/html/rfc7230#section-6.1
fetch(new URL(pathname, `https://${posthogHost}`), {
method: request.method ?? "",
headers,
...(!["HEAD", "GET"].includes(request.method ?? "")
? {
body: request,
duplex: "half",
}
: {}),
})
.then(async (res: Response) => {
const headers = new Headers(res.headers);
const body = await res.text();
// See https://github.com/nodejs/undici/issues/2514
if (headers.has("content-encoding")) {
headers.delete("content-encoding");
headers.delete("content-length");
}
response.writeHead(res.status, fromHeaders(headers));
response.end(body);
})
.catch((e: unknown) => {
next(new Error("Bad gateway", { cause: e }));
});
};
}

To use this proxy, you can create a server like this:

JavaScript
import http from "node:http";
import cors from "cors";
import proxy from "./proxy.js";
const corsMiddleware = cors({ origin: 'https://<your_app_domain>' });
const posthogMiddleware = proxy({ prefix: "/<ph_proxy_path>" });
const server = http.createServer((req, res) => {
corsMiddleware(req, res, (err) => {
if (err) {
// Some error handling for errors returned by the CORS middleware.
} else {
posthogMiddleware(req, res, (err) => {
if (err) {
// Some error handling for errors returned by the posthog middleware.
} else {
// Your next handler ...
// handler(req, res);
}
});
}
});
});

Once done, you can initialize the PostHog client with the proxy path:

JavaScript
import { PostHog } from 'posthog-node'
const client = new PostHog(
'<ph_project_api_key>',
{ host: '/<ph_proxy_path>' }
)

Thank you to SimonSimCity for this method.

Questions? Ask Max AI.

It's easier than reading through 718 pages of documentation

Community questions

Was this page useful?

Next article

Using Nuxt routeRules as a reverse proxy

Nuxt 3 uses Nitro under the hood, which provides the routeRules config that can be used to proxy requests from one route to another. To do this, add the following routeRules to your nuxt.config.ts file: Then configure the PostHog client to send requests via your new proxy:

Read next article