TL;DR / Executive Summary
I wanted to understand my blog’s traffic without using traditional, privacy-invasive analytics tools. I built a custom solution using an Nginx Ingress in Kubernetes to intercept requests, a Go service to handle authentication and set a tracking cookie, and OpenTelemetry to send trace data to a self-hosted SigNoz instance. This gives me powerful, detailed insights into user journeys while maintaining full control over the data.
The Problem: Understanding Traffic Without Sacrificing Privacy
As a tech blogger, I want to know which of my posts are popular and where my readers are coming from. Are they clicking links from my social media, a newsletter, or another site? Answering this is key to understanding what content resonates. However, most off-the-shelf analytics tools, like Google Analytics, come at a cost: user privacy.
My goal was to build a system that could:
- Track a user’s journey through the blog.
- Identify the initial source of the traffic.
- Avoid collecting personal data.
- Give me full ownership and control over the analytics data.
The Solution: A K8s-Native Tracking and Observability Pipeline
I built a system that leverages the power of Kubernetes, Nginx, and modern observability tools. The core idea is to use the Nginx Ingress Controller’s auth_request feature to inspect every incoming request. This request is sent to a small Go service that checks for a tracking token, sets a cookie, and generates a trace for the interaction.
Here is a diagram of the request flow:
Here’s how the components work together:
- Nginx Ingress Controller: Acts as the gatekeeper. It’s configured to send an authentication sub-request to our Go service for every user request.
- Go Auth Service: A lightweight service that checks for a tracking token. If valid, it sets a cookie and returns a
200 OKstatus to Nginx. - OpenTelemetry & SigNoz: The Go service is instrumented with OpenTelemetry to send detailed traces of every request to a SigNoz collector for analysis.
Step 1: Configuring Nginx Ingress
The first step is to tell our Nginx Ingress to use our Go service for authentication. This is done with a few simple annotations on the Ingress resource.
# This annotation points to our internal Go service.
"nginx.ingress.kubernetes.io/auth-url": "http://auth-service.auth-service.svc.cluster.local/auth"
# This snippet ensures the original request URI is passed to the auth service,
# so we can parse the token from the URL on the first visit.
"nginx.ingress.kubernetes.io/auth-snippet": "proxy_set_header X-Original-URI $request_uri;"
Step 2: The Authentication and Tracking Logic (Go-Style Pseudocode)
This is the heart of the system. Here is the core logic in Go-style pseudocode, which is instrumented with OpenTelemetry at each step.
// authHandler checks for a valid tracking token.
func authHandler(w http.ResponseWriter, r *http.Request) {
// Start an OpenTelemetry span to trace this function.
ctx, span := tracer.Start(r.Context(), "CheckingAuthToken")
defer span.End()
const validToken = "hardcoded123"
// Extract token from header, URL query, and cookie.
headerToken := r.Header.Get("X-Static-Token")
queryToken := r.URL.Query().Get("token")
cookie, _ := r.Cookie("X-Static-Token")
var cookieToken string
if cookie != nil {
cookieToken = cookie.Value
}
// Check if any of the tokens are valid.
if headerToken == validToken || queryToken == validToken || cookieToken == validToken {
// If valid, set a cookie for future requests.
http.SetCookie(w, &http.Cookie{
Name: "X-Static-Token",
Value: validToken,
Path: "/",
})
// Return HTTP 200 OK to Nginx to allow the request.
w.WriteHeader(http.StatusOK)
} else {
// If no valid token is found, block the request.
w.WriteHeader(http.StatusUnauthorized)
}
}
When a user first visits with a special link like my.blog/post?token=hardcoded123, the service identifies the token, sets the X-Static-Token cookie, and creates a trace. For all future page loads, the browser will send the cookie, allowing us to tie the activity to the same user session.
Step 3: Visualizing the Data in SigNoz
All trace data is sent to our self-hosted SigNoz instance. In the SigNoz UI, we can now see every request that hits our blog, build dashboards to visualize traffic patterns, and trace a user’s journey through the site—all without collecting any personal data.
Conclusion
This setup provides a powerful, self-hosted, and privacy-respecting way to analyze blog traffic. By combining standard Kubernetes components with the observability power of OpenTelemetry and SigNoz, I have a flexible and insightful analytics system that I completely control.