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:

  1. Track a user’s journey through the blog.
  2. Identify the initial source of the traffic.
  3. Avoid collecting personal data.
  4. 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:

graph TD subgraph "User's Browser" A[" User visits blog.com"] end subgraph "Kubernetes Cluster" B[" Nginx Ingress"] C[" Go Auth Service"] D[" Blog Content"] E[" SigNoz"] end A --> B B -->|1 step — Auth Request & Check Token| C C -->|3 step — Send Trace| E C -->|4 step — Return 200 OK with Set Cookie| B B -->|5 step — Serve Content| D D --> A classDef user fill:#A9A9A9,stroke:#333,stroke-width:2px,color:#fff classDef k8s fill:#4A90E2,stroke:#333,stroke-width:2px,color:#fff classDef app fill:#50E3C2,stroke:#333,stroke-width:2px,color:#fff classDef storage fill:#F5A623,stroke:#333,stroke-width:2px,color:#fff classDef monitoring fill:#9013FE,stroke:#333,stroke-width:2px,color:#fff class A user class B,D k8s class C app class E monitoring

Here’s how the components work together:

  1. Nginx Ingress Controller: Acts as the gatekeeper. It’s configured to send an authentication sub-request to our Go service for every user request.
  2. Go Auth Service: A lightweight service that checks for a tracking token. If valid, it sets a cookie and returns a 200 OK status to Nginx.
  3. 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.