Kubernetes reverse proxy
Contents
- 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.comfor US,eu.i.posthog.comfor 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 a Kubernetes Ingress Controller to proxy PostHog.
If your team already uses Kubernetes, this method lets you set up a reverse proxy using Kubernetes resources instead of deploying a separate tool like Caddy or Nginx.
How it works
Kubernetes Ingress Controllers can proxy traffic to external domains, not just internal services. You'll use ExternalName services to tell Kubernetes about PostHog's domains, then create an Ingress that routes traffic through those services.
Here's the request flow:
- User triggers an event in your app
- Request goes to your subdomain (e.g.,
e.yourdomain.com) - Your Ingress Controller receives the request
- Ingress routes the request to an ExternalName service
- ExternalName service resolves PostHog's domain (e.g.,
us.i.posthog.com) - Ingress Controller proxies the request to PostHog with correct headers
- PostHog processes the event and returns a response
Why two services? PostHog uses separate domains for API requests and static assets. You need two ExternalName services:
- Main service: Points to
us.i.posthog.comoreu.i.posthog.comfor event capture, feature flags, and API calls - Assets service: Points to
us-assets.i.posthog.comoreu-assets.i.posthog.comfor the JavaScript SDK and other static files
What's an ExternalName service? It's a Kubernetes service type that maps a service name to an external DNS name. When your Ingress references the service, Kubernetes resolves the external domain and the Ingress Controller proxies to it.
This approach works because Ingress Controllers handle TLS termination, header manipulation, and upstream SSL connections to external domains.
Prerequisites
- A Kubernetes cluster with an Ingress Controller installed, such as ingress-nginx, Traefik, or HAProxy
kubectlconfigured to access your cluster- A domain with DNS records pointing to your Ingress Controller's load balancer
- A TLS certificate stored as a Kubernetes Secret (or cert-manager configured to provision one)
Choose your setup option
All three options accomplish the same goal of routing PostHog through your domain. Choose based on your existing Kubernetes infrastructure:
- Option 1: ingress-nginx: The most common Ingress Controller with complete configuration. Use this if you're using nginx-based ingress or starting fresh.
- Option 2: Traefik, HAProxy, or other controllers: Uses the same approach as Option 1 with controller-specific annotations. Use this if you're already running a different Ingress Controller.
- Option 3: Istio service mesh: For teams using Istio who want path-based routing (e.g.,
yourdomain.com/posthog). More complex but integrates with existing service mesh.
Option 1: ingress-nginx
This setup works with the ingress-nginx controller, the most widely used Ingress Controller in Kubernetes.
- 1
Create a configuration file
Create a file named
posthog-proxy.yaml:YAMLReplace these values:
e.yourdomain.com: Your subdomain for the proxyyour-tls-secret: Your Kubernetes TLS secret nameus.i.posthog.comandus-assets.i.posthog.com: Change toeu.i.posthog.comandeu-assets.i.posthog.comfor EU region
The annotations in the Ingress configuration above do the following:
upstream-vhostsets the Host header sent to PostHog. Without this, PostHog receives your subdomain as the Host header and can't route the request, causing 401 errors.backend-protocoltells the Ingress Controller to use HTTPS when connecting to PostHog's domains.configuration-snippetconfigures SSL settings for the upstream connection. Theproxy_ssl_nameensures proper SNI handling.
- 2
Apply the configuration
Deploy the resources to your cluster:
TerminalVerify the resources were created:
TerminalThe Ingress should show your subdomain in the HOSTS column. The services should show the PostHog domains in the EXTERNAL-NAME column.
- 3
Update your PostHog SDK
In your application code, update your PostHog initialization to use your proxy subdomain:
Replace
e.yourdomain.comwith your actual subdomain.The
ui_hostmust point to PostHog's actual domain so features like the toolbar link correctly. Verify your setup
CheckpointConfirm events are flowing through your proxy:
- Check that your Ingress has an IP address assigned:
Terminal- Test connectivity from within your cluster:
TerminalYou should see a
200 OKresponse.- In your application, open your browser's developer tools and go to the Network tab
- Trigger an event, like a page view
- Look for a request to your subdomain (e.g.,
e.yourdomain.com) - Verify the response is
200 OK - Check the PostHog app to confirm events appear
If you see errors, check troubleshooting below.
Option 2: Traefik, HAProxy, or other controllers
If you're using a different Ingress Controller, use the same resource structure as Option 1 but with controller-specific annotations.
- 1
Find your controller's annotations
Each Ingress Controller uses different annotations for upstream configuration. Consult your controller's documentation:
You need annotations that handle:
- Setting the Host header for upstream requests
- Using HTTPS for backend connections
- Configuring SSL/TLS for upstream connections
- 2
Create your configuration file
Here's a Traefik example. Create
posthog-proxy.yaml:YAMLThe ExternalName services are identical across all Ingress Controllers. Only the Ingress annotations change.
- 3
Apply the configuration
Deploy the resources:
TerminalVerify the resources were created:
Terminal - 4
Update your PostHog SDK
In your application code, update your PostHog initialization:
Replace
e.yourdomain.comwith your actual subdomain. Verify your setup
CheckpointConfirm events are flowing through your proxy:
- Check that your Ingress has an IP address assigned:
Terminal- Test connectivity from within your cluster:
TerminalYou should see a
200 OKresponse.- In your application, open your browser's developer tools and go to the Network tab
- Trigger an event, like a page view
- Look for a request to your subdomain
- Verify the response is
200 OK - Check the PostHog app to confirm events appear
If you see errors, check troubleshooting below.
Option 3: Istio service mesh
If you're using Istio, you can set up path-based routing to proxy PostHog through a subpath on your domain (e.g., yourdomain.com/posthog).
This option is more complex and assumes you're already familiar with Istio concepts like VirtualServices, ServiceEntries, and DestinationRules.
- 1
Create Istio configuration
Create a file named
posthog-istio.yaml:YAMLReplace these values:
your-namespace: Your Kubernetes namespaceyour-gateway: Your Istio Gateway nameyourdomain.com: Your domain/posthog/: Your desired subpathus-proxy-direct.i.posthog.com: Change toeu-proxy-direct.i.posthog.comfor EU region
The VirtualService matches requests to
/posthog/, rewrites the URI to/, and routes to PostHog with the correct Host header. - 2
Apply the configuration
Deploy the Istio resources:
TerminalVerify the resources were created:
Terminal - 3
Update your PostHog SDK
In your application code, update your PostHog initialization to use your subpath:
Replace
yourdomain.com/posthogwith your actual domain and subpath. Verify your setup
CheckpointConfirm events are flowing through your proxy:
- Check your VirtualService status:
Terminal- Test connectivity from within your mesh:
TerminalYou should see a
200 OKresponse.- In your application, open your browser's developer tools and go to the Network tab
- Trigger an event, like a page view
- Look for a request to your domain with
/posthogpath - Verify the response is
200 OK - Check the PostHog app to confirm events appear
If you see errors, check troubleshooting below.
Troubleshooting
Ingress shows no address
If your Ingress doesn't have an address assigned:
Check that your Ingress Controller is running:
If the controller isn't running, you need to install it first. See your controller's installation guide.
502 Bad Gateway errors
If you see 502 Bad Gateway responses, your Ingress Controller can't reach PostHog's domains. This usually means:
- DNS resolution failing: Check that your cluster can resolve PostHog's domains:
- Network policies blocking egress: Check if network policies prevent external traffic:
You may need to create a policy allowing egress to PostHog's domains.
- Wrong PostHog region: Verify you're using the correct region (
usoreu) matching your PostHog project.
401 Unauthorized errors
If PostHog returns 401 Unauthorized:
- Verify the
upstream-vhostannotation (or equivalent) is set to PostHog's domain - Check that your PostHog region matches in all places
- Confirm your project API key is correct in the SDK initialization
The Host header must be set to PostHog's domain (e.g., us.i.posthog.com), not your subdomain. Without this, PostHog can't authenticate your requests.
TLS certificate errors
If you see TLS errors like "certificate signed by unknown authority":
- Verify your TLS secret exists:
- Check that the secret contains valid certificate data:
- If using cert-manager, check the certificate status:
Static assets return 404
If the PostHog SDK fails to load or you see 404 errors for /static/* requests:
- Verify you created the
posthog-assets-proxyservice:
- Check that the service points to the correct assets domain:
- Verify your Ingress has a
/staticpath rule before the catch-all/rule. Ingress path matching is order-dependent.
Events aren't reaching PostHog
If events don't appear in PostHog:
- Check Ingress Controller logs for errors:
- Verify your ExternalName services resolve correctly:
- Test the proxy from within your cluster:
- Check your application's network tab for the actual error response from PostHog