Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# General
.DS_Store
.env
*.log

# Nginx runtime files (not used in repo)
client_body_temp/
proxy_temp/
fastcgi_temp/
uwsgi_temp/
scgi_temp/
106 changes: 24 additions & 82 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,94 +1,36 @@
# Nginx Proxy for Mixpanel
An example nginx config that serves as a proxy to Mixpanel's Ingestion API and JavaScript library endpoints. To learn more, visit our [docs on Tracking via Proxy](https://docs.mixpanel.com/docs/tracking/how-tos/tracking-via-proxy).
# 🚀 Mixpanel Tracking Proxy

A lightweight nginx-based HTTP proxy server for Mixpanel tracking API, forked and customized for enhanced tracking capabilities. 📊

## Installation
## 📋 Overview

There are a few ways you can use this repo to deploy a server that can be use to proxy Mixpanel API requests:
This proxy acts as an intermediary between client applications and Mixpanel's API endpoints, enabling:

1. **one-click deploy** to your cloud provider
2. **build a docker image** and run it on your own servers
3. **copy and paste** the nginx settings to your existing nginx config file
- **🎯 Tracking API Proxy**: Forwards requests to `api.mixpanel.com` with proper header management
- **📚 JavaScript Library Proxy**: Serves Mixpanel's client libraries via `/lib.js` and `/lib.min.js`
- **🔀 Decision API Proxy**: Handles feature flag requests via `/decide` endpoint
- **🌍 IP Preservation**: Maintains accurate geolocation tracking through sophisticated IP forwarding
- **🌐 Multi-Region Support**: Configurable for EU and IN data residency requirements

### Option 1: One-Click Deploy
click on a button below to deploy to your favorite cloud provider:

[![Google Cloud Btn]][Google Cloud Deploy]
[<img src=https://www.deploytodo.com/do-btn-blue.svg width=198px />][Digital Ocean Deploy]
[![Railway Btn]][Railway Deploy]
[![Render Btn]][Render Deploy]


<!-- URLS -->
[Google Cloud Btn]: https://binbashbanana.github.io/deploy-buttons/buttons/remade/googlecloud.svg
[Google Cloud Deploy]: https://deploy.cloud.run

[Digital Ocean Btn]: https://www.deploytodo.com/do-btn-blue.svg
[Digital Ocean Deploy]: https://cloud.digitalocean.com/apps/new?repo=https://github.com/mixpanel/tracking-proxy/tree/master

[Railway Btn]: https://binbashbanana.github.io/deploy-buttons/buttons/remade/railway.svg
[Railway Deploy]: https://railway.app/template/_RaWSW

[Render Btn]: https://binbashbanana.github.io/deploy-buttons/buttons/remade/render.svg
[Render Deploy]: https://render.com/deploy?repo=https://github.com/mixpanel/tracking-proxy


<!-- Maybe later? -->

<!-- Heroku's app.json conflicts with GCP 0_o -->
[Heroku Btn]: https://binbashbanana.github.io/deploy-buttons/buttons/remade/heroku.svg
[Heroku Deploy]: https://heroku.com/deploy/?template=https://github.com/mixpanel/tracking-proxy

<!-- Azure is too... complicated -->
[Azure Btn]: https://binbashbanana.github.io/deploy-buttons/buttons/remade/azure.svg
[Azure Deploy]: https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FYOUR_GITHUB_USERNAME%2FYOUR_REPO_NAME%2FYOUR_BRANCH_NAME%2Fpath%2Fto%2Fazuredeploy.json




### Option 2: Docker Image
Assuming you have Docker installed on your system, you can do the following:

1. Clone the repo
`git clone https://github.com/mixpanel/tracking-proxy`
2. Build the Docker image:
`docker build -t mixpanel-proxy .`
3. Run a container using the image:
`docker run --name my-tracking-proxy -d -p 8080:80 mixpanel-proxy`
4. Visit
`http://localhost:8080`

You should see:

```json
{
"error": "Welcome. Get started with our API by visiting https://developer.mixpanel.com/"
}
```
This is same response you would get from visiting https://api.mixpanel.com/ (which means your proxy is working as expected).

You can also verify the nginx config on the command line:
## ⚡ Quick Start

### 🐳 Docker Deployment
```bash
nginx -t -c /etc/nginx/nginx.conf
docker build -t mixpanel-proxy .
docker run -p 8080:80 mixpanel-proxy
```

For production, you would deploy this docker image to whatever servers you run your production services on.

### Option 3: Add locations to your existing Nginx config
If you already have servers running nginx, you can copy and paste the locations from the [nginx.conf](https://github.com/mixpanel/tracking-proxy/blob/master/nginx.conf) file in this repo and adjust the locations to match your preference.

### Additional Configuration
In case you see events tracked to your project, but geo-location properties are not being added correctly, there's likely a configuration issue with passing the IP address through the headers. Within the [nginx configuration]([url](https://github.com/mixpanel/tracking-proxy/blob/2c08e999d4b38aa943fad55884bcfe0ef72bb681/nginx.conf#L31)), by default, we leverage `$http_x_forwarded_for` and pass it through the `X-Real-IP` header to forward the IP from the original client; in some providers, this is not available and you will want to review which header/variable provides the IP address from the client. As an example, in Cloudflare, you need to pass `$realip_remote_addr` instead. You'll want to review the option that applies to your cloud provider.

When you're seeing events ingested into Mixpanel, but no geo-location data included as properties (you don't see the mp_country_code property created, for example), one more aspect to check is if the query string param/value `ip=1` is being passed to our ingestion endpoint. Our client-side libraries, by default (unless you've disabled geo-location tracking), will append `ip=1` to the URL of the ingestion endpoint to indicate we should parse geo-location from the IP address of the incoming request. In some setups, this param might not be included in the request from the proxy, so it's also an aspect to check in this situation.

### EU Residency
This proxy server resolves requests to `api.mixpanel.com`, which points to Mixpanel's primary data centers in the United States. If you are using Mixpanel's **[EU Data Residency](https://docs.mixpanel.com/docs/other-bits/privacy-and-security/eu-residency)**, you will need to change the [nginx.config](https://github.com/mixpanel/tracking-proxy/blob/master/nginx.conf#L34) from `api.mixpanel.com` to `api-eu.mixpanel.com`
### ⚙️ Configuration
The nginx configuration includes:
- ☁️ Cloudflare and Render.com IP range support
- 🐛 Debug headers for troubleshooting
- ⚡ High-performance settings with connection backlog optimization

### IN Residency
This proxy server resolves requests to `api.mixpanel.com`, which points to Mixpanel's primary data centers in the United States. If you are using Mixpanel's **[IN Data Residency](https://docs.mixpanel.com/docs/privacy/in-residency)**, you will need to change the [nginx.config](https://github.com/mixpanel/tracking-proxy/blob/master/nginx.conf#L34) from `api.mixpanel.com` to `api-in.mixpanel.com`
## ✨ Features

### Load Testing
- **🔍 Client IP Preservation**: Advanced IP detection and forwarding logic
- **📝 Debug Logging**: Comprehensive request debugging capabilities
- **⚖️ Load Balancing Ready**: Optimized for high-concurrency environments
- **☁️ Cloud Provider Agnostic**: Works across different hosting platforms

If you wish to load test your proxy, see **[mp-proxy-load-test](https://github.com/ak--47/mp-proxy-load-test/)** for a load testing script with artillery.
🍴 Forked from the original Mixpanel tracking proxy with enhanced debugging and IP handling capabilities.
103 changes: 68 additions & 35 deletions nginx.conf
Original file line number Diff line number Diff line change
@@ -1,76 +1,109 @@
# nginx configuration for Mixpanel proxy
#
# Goals
# - Keep the config simple and well documented
# - Correctly forward the end-user IP when running on Render.com (which may sit behind Cloudflare)
# - Handle Cloudflare headers appropriately and follow nginx best practices
#
# How client IP is determined
# - Cloudflare sets CF-Connecting-IP to the original client IP.
# - We trust connections coming from Cloudflare ranges and Render's internal network.
# - We use real_ip_header CF-Connecting-IP so nginx treats the client IP as the real remote address
# when requests come through these trusted proxies.
# - For headers we pass upstream, we use a $client_ip variable that prefers CF-Connecting-IP and
# falls back to $remote_addr otherwise.

# Minimal events block
events {}

http {
# Cloudflare real IP configuration
# Trust Cloudflare edge ranges (IPv4)
# Reference: https://www.cloudflare.com/ips/
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
# Render.com internal IPs (10.223.x.x range)
set_real_ip_from 131.0.72.0/22;

# Trust Cloudflare edge ranges (IPv6)
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;

# Trust Render.com internal network (requests proxied within Render to your service)
# Render docs commonly show 10.223.0.0/16 for internal hops.
set_real_ip_from 10.223.0.0/16;

# Use Cloudflare's header as the source of truth for client IP when coming from trusted ranges
real_ip_header CF-Connecting-IP;
real_ip_recursive on;

# Map to get the most reliable client IP
# Prefer CF-Connecting-IP for X-Real-IP, otherwise fall back to the socket remote address
map $http_cf_connecting_ip $client_ip {
"" $remote_addr;
default $http_cf_connecting_ip;
"" $remote_addr;
}

# Debug logging format to see all IP variables
log_format debug_ips '$remote_addr - $realip_remote_addr - $http_cf_connecting_ip - $http_x_forwarded_for - $http_x_real_ip';
# Debug access log format to help verify IP handling
log_format debug_ips 'remote=$remote_addr realip=$realip_remote_addr cf=$http_cf_connecting_ip xff=$http_x_forwarded_for xreal=$http_x_real_ip';

server {
listen 80 default backlog=16384;
listen [::]:80 default backlog=16384;
# Listen on IPv4 and IPv6, set as default server and raise backlog for high concurrency
listen 80 default_server backlog=16384;
listen [::]:80 default_server backlog=16384;

# Enable debug logging
# Emit debug access logs to stdout (visible in container logs)
access_log /dev/stdout debug_ips;

# Common proxy headers for all locations
proxy_set_header X-Real-IP $client_ip; # Prefer end-user IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Append our hop
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;

# Ensure proper SNI when proxying to HTTPS upstreams
proxy_ssl_server_name on;

# Mixpanel library files from CDN
location /lib.min.js {
proxy_set_header X-Real-IP $client_ip;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_pass https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js;
}

location /lib.js {
proxy_set_header X-Real-IP $client_ip;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_pass https://cdn.mxpnl.com/libs/mixpanel-2-latest.js;
}

# Mixpanel decide endpoint
location /decide {
proxy_set_header Host decide.mixpanel.com;
proxy_set_header X-Real-IP $client_ip;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass https://decide.mixpanel.com/decide;
}

# Default: Mixpanel tracking API
location / {
proxy_set_header Host api.mixpanel.com;
proxy_set_header X-Real-IP $client_ip;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Proto $scheme;

# Comprehensive debug headers
proxy_set_header X-Debug-RemoteAddr $remote_addr;
proxy_set_header X-Debug-RealIP $realip_remote_addr;
proxy_set_header X-Debug-CFConnectingIP $http_cf_connecting_ip;
proxy_set_header X-Debug-XForwardedFor $http_x_forwarded_for;
proxy_set_header X-Debug-XRealIP $http_x_real_ip;
proxy_set_header X-Debug-UserAgent $http_user_agent;
proxy_set_header X-Debug-Referer $http_referer;
proxy_set_header X-Debug-TrueClientIP $http_true_client_ip;
proxy_set_header X-Debug-XForwardedForOriginal $http_x_forwarded_for_original;
proxy_set_header X-Debug-AllHeaders "$http_user_agent $http_referer $http_cf_connecting_ip $http_x_forwarded_for $http_x_real_ip $http_true_client_ip";

# Optional debug headers to help troubleshoot IP forwarding end-to-end.
# Comment these out in production if not needed.
proxy_set_header X-Debug-RemoteAddr $remote_addr;
proxy_set_header X-Debug-RealIP $realip_remote_addr;
proxy_set_header X-Debug-CFConnectingIP $http_cf_connecting_ip;
proxy_set_header X-Debug-XForwardedFor $http_x_forwarded_for;
proxy_set_header X-Debug-XRealIP $http_x_real_ip;

proxy_pass https://api.mixpanel.com/;
}
Expand Down