PostHog reverse-proxy with Express.js

Josiah Dudzik
3 min readApr 24, 2024

--

Recently I had a simple, yet specific challenge due to a minimalistic project architecture. I needed to add a reverse-proxy to my application to handle PostHog metrics because sometimes aggressive tracking/ad blockers will prevent PostHog requests from getting through. I’m effected by this as Brave Browser is my browser of choice!

Photo by Gavin Allanwood on Unsplash

PostHog has some pretty handy documentation for creating reverse-proxies already, but none of their solutions would work for me. However, the docs do let you know exactly what routes you need to send traffic to.

My challenge was this: because of the nature of my project, My system was limited to a static Next.js frontend and a Node/Express backend. I couldn’t simply spin-up an Nginx instance to reverse-proxy these requests, nor did it make sense to forward some domain traffic to another service like cloudflare or AWS just to handle this. So instead, I wanted to create a clean reverse-proxy in my Node/Express backend. It’d been a while since I’d written anything unique like this, so at first I fumbled around attempting to create a route that manually sent the correct data back and forth. Needless to say, that got really messy, really fast.

I stumbled on a NPM package, http-proxy, that was designed for making proxies in Node incredibly simple! Utilizing this package, along with knowing what routes I had to reverse-proxy for PostHog, this became a fairly simple task.

Hopefully this helps someone else who may have a similar necessity!
Prerequisites: I’m assuming you’re already familiar with both PostHog and Node/Express. I am also assuming you already have PostHog configured on your frontend system, whatever that may be.

For starters, PostHog requires that you reverse-proxy these routes and rewrite the “host” header to the domain you’re forwarding it to:

e.yourdomain.com/*     ->  https://us.i.posthog.com/*
e.yourdomain/static/* -> https://us-assets.i.posthog.com/static/*

So without further adieu, here is an example of a minimal Express server that has 2 routes that reverse-proxies the PostHog requests:

const express = require('express');
const httpProxy = require('http-proxy');

// Initiate Express and create a router.
const app = express();
const router = express.Router();

// Create a proxy server.
const proxy = httpProxy.createProxyServer();

// This is the method being used by the ingest route.
const posthogIngest = (req, res) => {
proxy.web(req, res, {
target: 'https://us.i.posthog.com',
changeOrigin: true,
secure: true,
xfwd: true,
headers: {
// These headers aren't necessary, but are useful for our metrics.
'X-Real-IP': req.ip,
'X-Forwarded-For': req.ip,
'X-Forwarded-Host': req.hostname,
},
});
};

// This is a very similar method, the only difference being that this
// is the target for static requests.
const posthogStatic = (req, res) => {
proxy.web(req, res, {
target: 'https://us-assets.i.posthog.com/static',
changeOrigin: true,
secure: true,
xfwd: true,
headers: {
'X-Real-IP': req.ip,
'X-Forwarded-For': req.ip,
'X-Forwarded-Host': req.hostname,
},
});
};

// In my case, I decided to use a "/collect" prefix for the proxy endpoints.
router.use('/collect/ingest', posthogIngest);
router.use('/collect/static', posthogStatic);

app.use(router);

app.listen(3001, () => {
console.log('Reverse-proxy server is running on port 3001');
});

Now from within your frontend system, you can modify your PostHog config to send requests to your domain instead of PostHog directly:

// ...

posthog.init(__POSTHOG_KEY__, {
api_host: 'https://e.yourdomain.com/collect',
});

// ...

And that’s about it! If everything is configured correctly, you should now have a built-in reverse-proxy in your Node/Express backend server.

Happy coding!

--

--