> For the complete documentation index, see [llms.txt](https://academy.cegedim.cloud/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://academy.cegedim.cloud/compute/containers-k8s/k8s-get-started/k8s-gateway-api-migration.md).

# Migrate to NGF Gateway API

Step-by-step migration plan from NGINX Ingress Controller to NGINX Gateway Fabric (NGF) on cegedim.cloud clusters

## Context

NGINX Ingress Controller (`ingress-nginx`) has reached end-of-life. The upstream project no longer receives feature development — security patches only, for a limited period. **NGINX Gateway Fabric (NGF)** is its replacement on cegedim.cloud, implementing the standard Kubernetes Gateway API.

This guide covers the migration of existing clusters from `ingress-nginx` to NGF.

{% hint style="info" %}
Clusters provisioned with **Traefik** or **Istio** already support Gateway API natively — this migration plan applies only to clusters using NGINX Ingress Controller (ingress-nginx).
{% endhint %}

For a full explanation of Gateway API and NGF, see [Gateway API](/compute/containers-k8s/k8s-features/gateway-api.md).

## RFC Process

Migration of existing clusters is handled via **two RFCs** — not one RFC per cluster:

| RFC       | Scope                       | Condition                                                                                  |
| --------- | --------------------------- | ------------------------------------------------------------------------------------------ |
| **RFC 1** | All non-production clusters | Validates the procedure across all non-prod environments in a single coordinated operation |
| **RFC 2** | All production clusters     | Applies the validated procedure to all prod clusters after successful non-prod RFC         |

## Current State

```mermaid
graph LR
    F5i[F5 Internal VIP] -->|port 80/443| Ni[nginx-int\nhostPort 80/443]
    F5e[F5 External VIP] -->|port 8081/8443| Ne[nginx-ext\nhostPort 8081/8443]

    Ni -->|Ingress rules| B1[backend-a]
    Ni -->|Ingress rules| B2[backend-b]
    Ne -->|Ingress rules| B3[backend-c]
    Ne -->|Ingress rules| B4[backend-d]

    subgraph ingress-nodes
        Ni
        Ne
    end
```

## Target State

```mermaid
graph LR
    F5i[F5 Internal VIP] -->|80/443| NGFi[NGINX pod\ninternal-gateway\nhostPort 80/443]
    F5e[F5 External VIP] -->|8081/8443| NGFe[NGINX pod\nexternal-gateway\nhostPort 8081/8443]

    NGFi -->|HTTPRoute| B1[backend-a]
    NGFi -->|HTTPRoute| B2[backend-b]
    NGFe -->|HTTPRoute| B3[backend-c]
    NGFe -->|HTTPRoute| B4[backend-d]

    subgraph nginx-gateway namespace
        CP[NGF control plane\nGatewayClass: nginx]
    end

    CP -.manages.-> NGFi
    CP -.manages.-> NGFe

    subgraph ingress-nodes
        NGFi
        NGFe
    end
```

## Day 1 — Platform Migration (Zero Customer Impact)

Day 1 is performed by the **cegedim.cloud platform team**. No changes to F5, no changes to customer Ingress resources, no service interruption.

### Traffic Flow During Day 1

```mermaid
graph LR
    F5i[F5 Internal VIP] -->|80/443 no change| NGFi[NGINX pod\nhostPort 80/443]
    F5e[F5 External VIP] -->|8081/8443 no change| NGFe[NGINX pod\nhostPort 8081/8443]

    NGFi -->|catch-all| Ni[nginx-int ClusterIP]
    NGFe -->|catch-all| Ne[nginx-ext ClusterIP]

    Ni -->|Ingress| B1[backend-a]
    Ni -->|Ingress| B2[backend-b]
    Ne -->|Ingress| B3[backend-c]
    Ne -->|Ingress| B4[backend-d]

    subgraph nginx-gateway
        CP[NGF control plane]
        CP -.manages.-> NGFi
        CP -.manages.-> NGFe
    end

    subgraph ingress-nginx
        Ni
        Ne
    end
```

### Day 1 Steps

1. Deploy NGF via Helm into the `nginx-gateway` namespace (new namespace — no conflict with existing resources)
2. Create the `internal-gateway` and `external-gateway` Gateway resources
3. Create the `ReferenceGrant` allowing HTTPRoutes in `nginx-gateway` to target Services in `ingress-nginx`
4. `nginx-int` releases hostPorts 80/443 — `internal-gateway` DaemonSet takes them
5. `nginx-ext` releases hostPorts 8081/8443 — `external-gateway` DaemonSet takes them
6. Create catch-all HTTPRoutes: `internal-gateway` → `nginx-int` ClusterIP, `external-gateway` → `nginx-ext` ClusterIP
7. **F5 config: no change. Customers: no impact.** Traffic flows NGF → nginx-int/nginx-ext → backends, unchanged.

{% hint style="warning" %}
**TLSRoute** is not supported in NGF (experimental channel, CRD not installed). Customers whose F5 does **not** terminate SSL and require raw TLS passthrough should open an ITCare ticket to discuss their specific case before RFC 1.
{% endhint %}

## Day 2+ — Application Migration Playbook

After Day 1, application teams can migrate their services **independently, in any order, at their own pace**. There is no deadline — legacy Ingress resources continue to work through the catch-all route until explicitly migrated.

### Per-Service Migration Steps

For each service:

1. Create an `HTTPRoute` with the exact hostname pointing directly to the backend `Service`
2. Delete the corresponding `Ingress` resource
3. Verify — hostname specificity ensures the new route takes precedence over the catch-all automatically

### Example: Simple Service Migration

```yaml
# Before (Ingress)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app1
  namespace: app1-ns
spec:
  rules:
  - host: app1.internal.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app1-service
            port:
              number: 8080
```

```yaml
# After (HTTPRoute)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app1
  namespace: app1-ns
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
  hostnames:
  - "app1.internal.example.com"       # exact match -> beats catch-all automatically
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: app1-service
      port: 8080
```

### Example: Partial Path Migration

Migrate a subset of paths for one hostname while the rest still flows through `nginx-int` via the catch-all:

```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app1-partial
  namespace: app1-ns
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
  hostnames:
  - "app1.internal.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /api/v2                # migrated path -> new backend
    backendRefs:
    - name: app1-v2-service
      port: 8080
  # Other paths for app1.internal.example.com still fall through to nginx-int via catch-all
```

### Annotation Migration Reference

| NGINX Ingress Annotation                             | NGF Equivalent                                 | Effort |
| ---------------------------------------------------- | ---------------------------------------------- | ------ |
| `rewrite-target: /$2` (prefix strip)                 | `URLRewrite` + `ReplacePrefixMatch`            | Low    |
| `rewrite-target: /static-path`                       | `URLRewrite` + `ReplaceFullPath`               | Low    |
| `use-regex: "true"`                                  | `path.type: RegularExpression`                 | Low    |
| `server-snippet` / `configuration-snippet` (headers) | `ResponseHeaderModifier` filter                | Low    |
| `permanent-redirect`                                 | `RequestRedirect` filter (statusCode: 301)     | Low    |
| `proxy-read/send/connect-timeout`                    | `UpstreamSettingsPolicy`                       | Low    |
| `proxy-body-size`                                    | `ClientSettingsPolicy`                         | Low    |
| `x-forwarded-prefix`                                 | `RequestHeaderModifier` filter                 | Low    |
| `auth-type: basic`                                   | `AuthenticationFilter` (alpha)                 | Medium |
| `whitelist-source-range`                             | `SnippetsFilter` (alpha)                       | Medium |
| Rate limiting (`limit-rps` / `limit-rpm`)            | `RateLimitPolicy` (alpha)                      | Medium |
| `use-regex` + capture group rewrite                  | `SnippetsFilter` (alpha) + `rewrite` directive | Medium |

{% hint style="info" %}
Alpha features (`AuthenticationFilter`, `RateLimitPolicy`, `SnippetsFilter`) are functional but carry API stability risk on NGF upgrades. `SnippetsFilter` is disabled by default and must be enabled by the platform team. Contact ITCare if your application uses these patterns.
{% endhint %}

## Use Case Examples

The following 15 use cases were validated on cegedim.cloud clusters. Each shows the original NGINX Ingress resource and its NGF equivalent.

{% hint style="info" %}
Use cases marked **(alpha)** rely on NGF-specific resources (`SnippetsFilter`, `AuthenticationFilter`, `RateLimitPolicy`, `ClientSettingsPolicy`) whose API may change on NGF upgrades. `SnippetsFilter` is disabled by default — open an ITCare ticket to request it.
{% endhint %}

### 01 — Prefix strip

Strip a path prefix before forwarding to the backend (e.g. `/myapp/hello` → `/hello`).

{% tabs %}
{% tab title="NGINX Ingress" %}

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: prefix-rewrite
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.mycluster.ccs.cegedim.cloud
    http:
      paths:
      - path: /myapp(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: my-service
            port:
              number: 80
```

{% endtab %}

{% tab title="NGF (HTTPRoute)" %}

```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: prefix-rewrite
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
  hostnames:
  - myapp.mycluster.ccs.cegedim.cloud
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /myapp
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /
    backendRefs:
    - name: my-service
      port: 80
```

{% endtab %}
{% endtabs %}

### 02 — Static path rewrite

Replace the entire request path with a fixed value (`/probe` → `/health`).

{% tabs %}
{% tab title="NGINX Ingress" %}

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: static-rewrite
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /health
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.mycluster.ccs.cegedim.cloud
    http:
      paths:
      - path: /probe
        pathType: Prefix
        backend:
          service:
            name: my-service
            port:
              number: 80
```

{% endtab %}

{% tab title="NGF (HTTPRoute)" %}

```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: static-rewrite
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
  hostnames:
  - myapp.mycluster.ccs.cegedim.cloud
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /probe
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplaceFullPath
          replaceFullPath: /health
    backendRefs:
    - name: my-service
      port: 80
```

{% endtab %}
{% endtabs %}

### 03 — Regex path match

Match paths using a regular expression (e.g. `/api/v1/resource` and `/api/v42/resource` match; `/api/vX/resource` does not).

{% hint style="warning" %}
`RegularExpression` is a Gateway API extended feature. When used with NGF, add `sectionName: http` to the `parentRef` to avoid an `InvalidListener` condition on the HTTPS listener.
{% endhint %}

{% tabs %}
{% tab title="NGINX Ingress" %}

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: regex-match
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.mycluster.ccs.cegedim.cloud
    http:
      paths:
      - path: /api/v[0-9]+/resource
        pathType: ImplementationSpecific
        backend:
          service:
            name: my-service
            port:
              number: 80
```

{% endtab %}

{% tab title="NGF (HTTPRoute)" %}

```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: regex-match
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
    sectionName: http
  hostnames:
  - myapp.mycluster.ccs.cegedim.cloud
  rules:
  - matches:
    - path:
        type: RegularExpression
        value: "/api/v[0-9]+/resource"
    backendRefs:
    - name: my-service
      port: 80
```

{% endtab %}
{% endtabs %}

### 04 — Security response headers

Inject standard security headers (`Content-Security-Policy`, `X-Content-Type-Options`, `X-XSS-Protection`) on every response.

{% tabs %}
{% tab title="NGINX Ingress" %}

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: security-headers
  annotations:
    nginx.ingress.kubernetes.io/server-snippet: |
      add_header Content-Security-Policy "default-src 'self'" always;
      add_header X-Content-Type-Options "nosniff" always;
      add_header X-XSS-Protection "1; mode=block" always;
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.mycluster.ccs.cegedim.cloud
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-service
            port:
              number: 80
```

{% endtab %}

{% tab title="NGF (HTTPRoute)" %}

```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: security-headers
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
  hostnames:
  - myapp.mycluster.ccs.cegedim.cloud
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    filters:
    - type: ResponseHeaderModifier
      responseHeaderModifier:
        set:
        - name: Content-Security-Policy
          value: "default-src 'self'"
        - name: X-Content-Type-Options
          value: nosniff
        - name: X-XSS-Protection
          value: "1; mode=block"
    backendRefs:
    - name: my-service
      port: 80
```

{% endtab %}
{% endtabs %}

### 05 — Basic authentication (alpha)

Protect a route with HTTP Basic Auth backed by an htpasswd secret.

{% hint style="warning" %}
The NGF secret type is `nginx.org/htpasswd`, different from the standard `Opaque` secret used by NGINX Ingress Controller.

```bash
kubectl create secret generic basic-auth-secret \
  --type=nginx.org/htpasswd \
  --from-literal=auth=$(htpasswd -nb admin password) \
 
```

{% endhint %}

{% tabs %}
{% tab title="NGINX Ingress" %}

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: basic-auth
  annotations:
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: basic-auth-secret
    nginx.ingress.kubernetes.io/auth-realm: "Protected Area"
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.mycluster.ccs.cegedim.cloud
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-service
            port:
              number: 80
```

{% endtab %}

{% tab title="NGF (HTTPRoute)" %}

```yaml
apiVersion: gateway.nginx.org/v1alpha1
kind: AuthenticationFilter
metadata:
  name: basic-auth
spec:
  type: Basic
  basic:
    realm: "Protected Area"
    secretRef:
      name: basic-auth-secret
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: basic-auth
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
  hostnames:
  - myapp.mycluster.ccs.cegedim.cloud
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    filters:
    - type: ExtensionRef
      extensionRef:
        group: gateway.nginx.org
        kind: AuthenticationFilter
        name: basic-auth
    backendRefs:
    - name: my-service
      port: 80
```

{% endtab %}
{% endtabs %}

### 06 — IP allowlist (alpha)

Allow requests only from specific CIDR ranges; deny all others with 403.

{% tabs %}
{% tab title="NGINX Ingress" %}

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ip-allowlist
  annotations:
    nginx.ingress.kubernetes.io/whitelist-source-range: "10.26.0.0/16,192.168.0.0/16"
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.mycluster.ccs.cegedim.cloud
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-service
            port:
              number: 80
```

{% endtab %}

{% tab title="NGF (HTTPRoute)" %}

```yaml
apiVersion: gateway.nginx.org/v1alpha1
kind: SnippetsFilter
metadata:
  name: ip-allowlist
spec:
  snippets:
  - context: http.server.location
    value: |
      allow 10.26.0.0/16;
      allow 192.168.0.0/16;
      deny all;
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: ip-allowlist
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
  hostnames:
  - myapp.mycluster.ccs.cegedim.cloud
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    filters:
    - type: ExtensionRef
      extensionRef:
        group: gateway.nginx.org
        kind: SnippetsFilter
        name: ip-allowlist
    backendRefs:
    - name: my-service
      port: 80
```

{% endtab %}
{% endtabs %}

### 07 — Permanent redirect

Redirect a path to a different host and/or path with HTTP 301.

{% tabs %}
{% tab title="NGINX Ingress" %}

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: redirect
  annotations:
    nginx.ingress.kubernetes.io/permanent-redirect: "https://new.example.com/landing"
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.mycluster.ccs.cegedim.cloud
    http:
      paths:
      - path: /old-path
        pathType: Prefix
        backend:
          service:
            name: my-service
            port:
              number: 80
```

{% endtab %}

{% tab title="NGF (HTTPRoute)" %}

```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: redirect
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
  hostnames:
  - myapp.mycluster.ccs.cegedim.cloud
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /old-path
    filters:
    - type: RequestRedirect
      requestRedirect:
        scheme: https
        hostname: new.example.com
        path:
          type: ReplaceFullPath
          replaceFullPath: /landing
        statusCode: 301
```

{% endtab %}
{% endtabs %}

### 08 — Proxy timeouts (alpha)

Set NGINX proxy timeouts per route: connect, send, and read.

{% tabs %}
{% tab title="NGINX Ingress" %}

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: timeouts
  annotations:
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "10"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.mycluster.ccs.cegedim.cloud
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-service
            port:
              number: 80
```

{% endtab %}

{% tab title="NGF (HTTPRoute)" %}

```yaml
apiVersion: gateway.nginx.org/v1alpha1
kind: SnippetsFilter
metadata:
  name: timeouts
spec:
  snippets:
  - context: http.server.location
    value: |
      proxy_connect_timeout 10s;
      proxy_send_timeout    60s;
      proxy_read_timeout    120s;
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: timeouts
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
  hostnames:
  - myapp.mycluster.ccs.cegedim.cloud
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    filters:
    - type: ExtensionRef
      extensionRef:
        group: gateway.nginx.org
        kind: SnippetsFilter
        name: timeouts
    backendRefs:
    - name: my-service
      port: 80
```

{% endtab %}
{% endtabs %}

### 09 — Request body size limit (alpha)

Limit the maximum request body size (e.g. reject uploads above 700 KB with 413).

{% tabs %}
{% tab title="NGINX Ingress" %}

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: body-size
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "700k"
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.mycluster.ccs.cegedim.cloud
    http:
      paths:
      - path: /upload
        pathType: Prefix
        backend:
          service:
            name: my-service
            port:
              number: 80
```

{% endtab %}

{% tab title="NGF (HTTPRoute)" %}

```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: body-size
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
  hostnames:
  - myapp.mycluster.ccs.cegedim.cloud
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /upload
    backendRefs:
    - name: my-service
      port: 80
---
apiVersion: gateway.nginx.org/v1alpha1
kind: ClientSettingsPolicy
metadata:
  name: body-size
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: body-size
  body:
    maxSize: 700k
```

{% endtab %}
{% endtabs %}

### 10 — Rate limiting (alpha)

Limit requests per client IP. The NGF `RateLimitPolicy` uses NGINX `limit_req` semantics — the reject code is configurable (503 on ingress-nginx by default, 429 with NGF).

{% tabs %}
{% tab title="NGINX Ingress" %}

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rate-limit
  annotations:
    nginx.ingress.kubernetes.io/limit-rps: "1"
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.mycluster.ccs.cegedim.cloud
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-service
            port:
              number: 80
```

{% endtab %}

{% tab title="NGF (HTTPRoute)" %}

```yaml
apiVersion: gateway.nginx.org/v1alpha1
kind: RateLimitPolicy
metadata:
  name: rate-limit
spec:
  targetRefs:
  - group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: rate-limit
  rateLimit:
    local:
      rules:
      - zoneSize: 10m
        key: "$binary_remote_addr"
        rate: 1r/s
    rejectCode: 429
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: rate-limit
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
  hostnames:
  - myapp.mycluster.ccs.cegedim.cloud
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: my-service
      port: 80
```

{% endtab %}
{% endtabs %}

### 11 — Regex capture group rewrite (alpha)

Reorder path segments using regex capture groups (e.g. `/foo/status/bar` → `/bar/foo`). There is no standard Gateway API equivalent — `SnippetsFilter` is required.

{% hint style="warning" %}
This is the most complex migration case. The `SnippetsFilter` injects a `location ~ /status/` block into the NGINX server context. The `HTTPRoute` must use `path.type: RegularExpression` (not `PathPrefix /`) to avoid a `location ^~` block that would prevent the injected regex location from matching. Replace `my-app_my-service_80` with the actual NGF upstream name: `{namespace}_{service-name}_{port}`.
{% endhint %}

{% tabs %}
{% tab title="NGINX Ingress" %}

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: regex-capture-rewrite
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$2/$1
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.mycluster.ccs.cegedim.cloud
    http:
      paths:
      - path: /(.+)/status/(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: my-service
            port:
              number: 80
```

{% endtab %}

{% tab title="NGF (HTTPRoute)" %}

```yaml
apiVersion: gateway.nginx.org/v1alpha1
kind: SnippetsFilter
metadata:
  name: regex-swap-rewrite
spec:
  snippets:
  - context: http.server
    value: |
      location ~ /status/ {
          rewrite ^/(.+)/status/(.*)$ /$2/$1 break;
          return 400;
          proxy_pass http://my-app_my-service_80;
      }
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: regex-swap-rewrite
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
    sectionName: http
  hostnames:
  - myapp.mycluster.ccs.cegedim.cloud
  rules:
  - matches:
    - path:
        type: RegularExpression
        value: /.*
    filters:
    - type: ExtensionRef
      extensionRef:
        group: gateway.nginx.org
        kind: SnippetsFilter
        name: regex-swap-rewrite
    backendRefs:
    - name: my-service
      port: 80
```

{% endtab %}
{% endtabs %}

### 12 — Dynamic headers via NGINX variables (alpha)

Inject headers using NGINX runtime variables (`$host`, `$request_id`, etc.) in responses to the client or in requests to the backend. The standard `ResponseHeaderModifier` and `RequestHeaderModifier` filters only support static values — use `SnippetsFilter` for dynamic ones.

{% tabs %}
{% tab title="NGINX Ingress" %}

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: escape-hatch
  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: |
      add_header X-Custom-Host $host always;
      add_header X-Custom-RequestID $request_id always;
      proxy_set_header X-Custom-Host $host;
      proxy_set_header X-Custom-RequestID $request_id;
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.mycluster.ccs.cegedim.cloud
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-service
            port:
              number: 80
```

{% endtab %}

{% tab title="NGF (HTTPRoute)" %}

```yaml
apiVersion: gateway.nginx.org/v1alpha1
kind: SnippetsFilter
metadata:
  name: escape-hatch
spec:
  snippets:
  - context: http.server.location
    value: |
      add_header X-Custom-Host $host always;
      add_header X-Custom-RequestID $request_id always;
      proxy_set_header X-Custom-Host $host;
      proxy_set_header X-Custom-RequestID $request_id;
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: escape-hatch
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
  hostnames:
  - myapp.mycluster.ccs.cegedim.cloud
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    filters:
    - type: ExtensionRef
      extensionRef:
        group: gateway.nginx.org
        kind: SnippetsFilter
        name: escape-hatch
    backendRefs:
    - name: my-service
      port: 80
```

{% endtab %}
{% endtabs %}

### 13 — Retry on upstream failure (alpha)

Transparently retry failed requests on another pod before returning an error to the client. Prevents a single unhealthy pod from causing 502 errors when healthy replicas are available.

{% tabs %}
{% tab title="NGINX Ingress" %}

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: next-upstream
  annotations:
    nginx.ingress.kubernetes.io/proxy-next-upstream: "error timeout"
    nginx.ingress.kubernetes.io/proxy-next-upstream-tries: "3"
    nginx.ingress.kubernetes.io/proxy-next-upstream-timeout: "0"
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.mycluster.ccs.cegedim.cloud
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-service
            port:
              number: 80
```

{% endtab %}

{% tab title="NGF (HTTPRoute)" %}

```yaml
apiVersion: gateway.nginx.org/v1alpha1
kind: SnippetsFilter
metadata:
  name: next-upstream
spec:
  snippets:
  - context: http.server.location
    value: |
      proxy_next_upstream error timeout;
      proxy_next_upstream_tries 3;
      proxy_next_upstream_timeout 0;
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: next-upstream
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
  hostnames:
  - myapp.mycluster.ccs.cegedim.cloud
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    filters:
    - type: ExtensionRef
      extensionRef:
        group: gateway.nginx.org
        kind: SnippetsFilter
        name: next-upstream
    backendRefs:
    - name: my-service
      port: 80
```

{% endtab %}
{% endtabs %}

### 14 — X-Forwarded-Prefix

Inform the backend of the public path prefix under which it is mounted. Required when the gateway strips a prefix and the backend generates absolute URLs or redirects (Spring Boot, Django, etc.).

{% hint style="info" %}
The `nginx.ingress.kubernetes.io/x-forwarded-prefix` annotation is unreliable since ingress-nginx v1.12+. Use `configuration-snippet` with `proxy_set_header` directly, as shown below.
{% endhint %}

{% tabs %}
{% tab title="NGINX Ingress" %}

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: x-forwarded-prefix
  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: |
      proxy_set_header X-Forwarded-Prefix "/myapp";
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.mycluster.ccs.cegedim.cloud
    http:
      paths:
      - path: /myapp
        pathType: Prefix
        backend:
          service:
            name: my-service
            port:
              number: 80
```

{% endtab %}

{% tab title="NGF (HTTPRoute)" %}

```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: x-forwarded-prefix
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
  hostnames:
  - myapp.mycluster.ccs.cegedim.cloud
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /myapp
    filters:
    - type: RequestHeaderModifier
      requestHeaderModifier:
        set:
        - name: X-Forwarded-Prefix
          value: /myapp
    backendRefs:
    - name: my-service
      port: 80
```

{% endtab %}
{% endtabs %}

### 15 — Real client IP from F5 (alpha)

Restore the actual client IP from the `X-Forwarded-For` header set by the F5 load balancer. Without this, backends see F5's IP on every request.

{% hint style="info" %}
As an alternative to `SnippetsFilter` (per-route scope), the platform team can configure real IP rewriting globally for all routes via an `NginxProxy` resource. Contact ITCare if a cluster-wide configuration is preferable.
{% endhint %}

{% tabs %}
{% tab title="NGINX Ingress" %}

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: use-forwarded-headers
  annotations:
    nginx.ingress.kubernetes.io/use-forwarded-headers: "true"
    nginx.ingress.kubernetes.io/proxy-real-ip-cidr: "10.0.0.0/8"
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.mycluster.ccs.cegedim.cloud
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-service
            port:
              number: 80
```

{% endtab %}

{% tab title="NGF (HTTPRoute)" %}

```yaml
apiVersion: gateway.nginx.org/v1alpha1
kind: SnippetsFilter
metadata:
  name: use-forwarded-headers
spec:
  snippets:
  - context: http.server
    value: |
      real_ip_header X-Forwarded-For;
      real_ip_recursive on;
      set_real_ip_from 10.0.0.0/8;
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: use-forwarded-headers
spec:
  parentRefs:
  - name: internal-gateway
    namespace: nginx-gateway
  hostnames:
  - myapp.mycluster.ccs.cegedim.cloud
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    filters:
    - type: ExtensionRef
      extensionRef:
        group: gateway.nginx.org
        kind: SnippetsFilter
        name: use-forwarded-headers
    backendRefs:
    - name: my-service
      port: 80
```

{% endtab %}
{% endtabs %}

## Summary Timeline

| Phase      | Who               | Description                                                                                                           |
| ---------- | ----------------- | --------------------------------------------------------------------------------------------------------------------- |
| **Day 1**  | Platform team     | NGF deployed; nginx-int/nginx-ext release hostPorts; catch-all HTTPRoutes created; F5 unchanged; zero customer impact |
| **Day 2+** | Application teams | Teams migrate Ingress resources to HTTPRoutes, service by service, in any order                                       |
| **Day N**  | Platform team     | `nginx-int` decommissioned once all internal traffic is fully migrated to NGF HTTPRoutes                              |
| **Day M**  | Platform team     | `nginx-ext` decommissioned once all external traffic is fully migrated to NGF HTTPRoutes                              |

## Support

To request migration assistance or report a compatibility issue, open a ticket via **ITCare** with the label `Gateway API Migration`.

For annotation-by-annotation migration guidance beyond the table above, refer to the full NGINX Gateway Fabric migration documentation:

{% embed url="<https://docs.nginx.com/nginx-gateway-fabric/how-to/migrate-from-nginx-ingress/>" %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://academy.cegedim.cloud/compute/containers-k8s/k8s-get-started/k8s-gateway-api-migration.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
