Francisco Giraldo

How the NGINX rewrite-target Annotation Simplifies Cluster Networking

Published 20 Oct 2026 10 min read

When you break a monolithic web backend into a decoupled microservices architecture, you run into an immediate structural problem: How do multiple independent applications share a single public domain name without clashing?

If you have an API and a background worker running in the same cluster, you don't want to force your API consumers to use completely different domains or track custom network ports (like port 3000 vs port 4000).

Instead, you want a unified routing gateway:

  • ://my-portfolio-domain.com -> Goes to the AI engine
  • ://my-portfolio-domain.com -> Goes to the text worker

The standard solution is to use a shared NGINX Ingress Controller to map routes based on the URL path. However, this introduces a classic backend pitfall: The Subpath 404 Error.

If the Ingress forwards the request path /api/predict straight to your backend app container as-is, your Express or Fastify router will throw a 404 Not Found error because your application code expects a clean root request (/predict).

Here is how to master the NGINX rewrite-target annotation to dynamically strip away external subpaths, allowing your microservices to communicate seamlessly behind a single gateway.


The Path Stripping Router Architecture

The NGINX Ingress Controller uses powerful Regular Expression (regex) captures to intercept incoming subpath strings at the front door, clean them up, and pass the normalized path down to the hidden app pods.

  External Traffic: http://my-domain.com
                             │
                             ▼
              ┌─────────────────────────────┐
              │ NGINX Ingress Front Gate    │ (Listens on single public IP)
              └──────────────┬──────────────┘
                             │
                             │ 1. Matches Path: /api/?(.*)
                             │ 2. Strips '/api' via regex capture
                             ▼
              ┌─────────────────────────────┐
              │ Private ClusterIP Service   │ (Internal network only)
              └──────────────┬──────────────┘
                             │
                             ▼
              ┌─────────────────────────────┐
              │   TypeScript App (app.ts)   │ (Receives clean '/' route)
              └─────────────────────────────┘

🛠️ Step-by-Step Implementation

To model a professional, enterprise-grade architecture, we pull the Ingress resource completely out of the individual microservice subfolders and place it in a centralized, global directory: infrastructure/global-ingress.yaml.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: global-microservices-ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "nginx"

    # 🧠 THE MAGIC ENGINE: Captures the regex group $1 and overrides the request path
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
    - host: my-local-ai-service.com
      http:
        paths:
          # 🤖 ROUTE 1: Forward /api/predict to the AI Inference application
          - path: /api/?(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: my-ai-app-service # Private ClusterIP service name
                port:
                  number: 80

          # 📊 ROUTE 2: Forward /processor/process to the Data Processor worker
          - path: /processor/?(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: my-processor-service # Private ClusterIP service name
                port:
                  number: 80

🧠 Decoding the Regex Handshake: /?(.*) and /$1

How exactly does this text transformation operate under the hood?

  1. The Capture Group /?(.*): When a user triggers an API request to /api/predict, the Ingress regex parser matches the route. The /? marks the trailing slash as optional, and the (.*) captures everything that follows it (in this case, predict) and saves it inside an internal variable named $1.
  2. The Rewrite Target /$1: The NGINX server strips the original path string away entirely, handles the internal network jump, and rewrites the incoming path to matching /$1 (which maps to /predict).
  3. Decoupled Application Logic: Your TypeScript application code remains completely clean and isolated. It doesn't need to know it is hosted behind a proxy gateway or a subpath sub-tree. It simply boots up, runs its strategy patterns, and responds on its native endpoints.

🚀 The Architectural Benefits

  • Clean Domain Preservation: Your entire multi-service ecosystem operates under a single domain name, dramatically simplifying SSL/TLS certification setups (HTTPS handling at the front door) and frontend API integrations.
  • Granular Network Isolation: Your actual business logic applications are hidden cleanly behind ClusterIP networks. They are entirely inaccessible from the outside world except through the specific paths explicitly approved inside the global-ingress.yaml manifest.

Conclusion

Mastering the Ingress rewrite target is the key to decoupling networking from business logic. By implementing centralized path routing, you transform isolated container apps into a unified, enterprise-ready microservices mesh.




Keep reading other of my blog posts!