Access Control Policy in NGINX Ingress Controller: Patterns for Ingress

by

in ,

NGINX Ingress Controller lets you define IP-based access rules once in a Policy resource and apply them consistently across your Ingress traffic paths.

Across this blog, we’re focused on:

  • How Access Control policy works in NGINX Ingress Controller.
  • Where to attach it in Ingress.
  • Patterns for allowlists, denylists, and per-route policies.

Why Use a Policy for Access Control?

Many teams manage IP restrictions through cloud firewalls or raw NGINX config snippets and quickly end up with drift. Using a dedicated Policy for access control gives you:

  • A single source of truth for allowed and denied IP ranges.
  • Reuse across services and namespaces.
  • Cleaner reviews because access rules are isolated from route logic.
  • Version-controlled YAML alongside your application code.

How Access Control Policy Works in NGINX Ingress Controller

At a high level:

  1. Create a Policy resource with spec.accessControl.
  2. Attach it to an Ingress via the nginx.org/policies annotation.
  3. NGINX Ingress Controller renders the corresponding allow/deny directives. Non-matching requests receive a 403 Forbidden response.

Allowlist policy

An allow policy permits only the listed CIDR ranges and rejects everything else:

apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
  name: webapp-policy
spec:
  accessControl:
    allow:
    - 10.0.0.0/8

Denylist policy

A deny policy blocks the listed ranges and permits everything else:

apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
  name: webapp-policy-deny
spec:
  accessControl:
    deny:
    - 203.0.113.0/24

Attaching to an Ingress

Reference the policy in the nginx.org/policies annotation:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cafe-ingress
  annotations:
    nginx.org/policies: "webapp-policy"
spec:
  ingressClassName: nginx
  rules:
  - host: cafe.example.com
    http:
      paths:
      - path: /tea
        pathType: Prefix
        backend:
          service:
            name: tea-svc
            port:
              number: 80
      - path: /coffee
        pathType: Prefix
        backend:
          service:
            name: coffee-svc
            port:
              number: 80

Multiple policies can be comma-separated:

annotations:
  nginx.org/policies: "webapp-policy, webapp-policy-deny"

Important behavior to remember:

  • A policy referenced at the Ingress level applies to all paths in that resource.
  • To apply different policies to different routes, use separate Ingress resources for each path (see below).
  • When combining allow and deny policies, NGINX evaluates them in order.

Per-Route Policies with Separate Ingress Resources

When different routes need different access rules, split them into separate Ingress resources. NGINX Ingress Controller merges them into a single configuration.

Locked-down admin route:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: admin-ingress
  annotations:
    nginx.org/policies: "webapp-policy"
spec:
  ingressClassName: nginx
  rules:
  - host: cafe.example.com
    http:
      paths:
      - path: /tea
        pathType: Prefix
        backend:
          service:
            name: tea-svc
            port:
              number: 80

Public storefront with no policy:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: storefront-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: cafe.example.com
    http:
      paths:
      - path: /coffee
        pathType: Prefix
        backend:
          service:
            name: coffee-svc
            port:
              number: 80

Real-World Pattern: Locking Down an Admin Dashboard

A common production pattern combines multiple CIDR ranges to restrict an internal dashboard to corporate and VPN traffic only:

apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
  name: admin-only
  namespace: platform
spec:
  accessControl:
    allow:
    - 203.0.113.0/24    # Corporate office network
    - 198.51.100.10/32  # VPN exit node 1
    - 198.51.100.11/32  # VPN exit node 2
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: admin-dashboard
  namespace: platform
  annotations:
    nginx.org/policies: "admin-only"
spec:
  ingressClassName: nginx
  rules:
  - host: admin.internal.yourcompany.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: admin-dashboard-svc
            port:
              number: 8080

This gives you defense in depth: blocked traffic never reaches your application containers, regardless of whether an attacker has valid credentials. Use this same pattern for staging environments, partner webhook endpoints, or per-tenant API restrictions.

Production Checks That Prevent Most Issues

Before rollout, validate these explicitly:

  • Non-allowed IPs receive a 403 and never reach backend pods.
  • If your cluster sits behind a cloud load balancer or proxy, configure NGINX Ingress Controller to use X-Forwarded-For or PROXY protocol so it sees the real client IP.
  • Use specific CIDR ranges. A /8 covers millions of IPs; prefer /24 or /32 in production.
  • When combining allow and deny policies, verify evaluation order matches your intent.

Security Considerations

Access Control operates at the Ingress edge, so keep it tight:

  • Use narrow CIDR ranges; avoid overly permissive allowlists.
  • Use /32 for known single-IP sources like VPN exit nodes.
  • Layer Access Control with application-level authentication for defense in depth.
  • Review Access Control policy changes with the same care as auth-related config changes.

You can find complete working examples on GitHub and more documentation in our docs:

NGINX Community Forum