Files
2025-08-19 15:20:23 +00:00

152 lines
4.3 KiB
Plaintext

---
title: Using node:http as a reverse proxy
sidebar: Docs
showTitle: true
---
import RegionWarning from "../_snippets/region-warning.mdx"
import ProxyWarning from "../_snippets/proxy-usage-warning.mdx"
import ProxyPathNamesWarning from "../_snippets/proxy-path-names-warning.mdx"
import { CalloutBox } from 'components/Docs/CalloutBox'
<CalloutBox icon="IconWarning" title="Warning" type="fyi">
1. <ProxyWarning />
2. <RegionWarning />
3. <ProxyPathNamesWarning />
</CalloutBox>
You can use Node's built-in `http` module to proxy requests to PostHog. This is a simple and lightweight way to proxy requests.
```ts
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:
```js
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:
```js
import { PostHog } from 'posthog-node'
const client = new PostHog(
'<ph_project_api_key>',
{ host: '/<ph_proxy_path>' }
)
```
> Thank you to [SimonSimCity](https://github.com/SimonSimCity) for this method.