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:
- Create a
Policyresource withspec.accessControl. - Attach it to an
Ingressvia thenginx.org/policiesannotation. - NGINX Ingress Controller renders the corresponding
allow/denydirectives. Non-matching requests receive a403 Forbiddenresponse.
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
Ingressresources 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
403and never reach backend pods. - If your cluster sits behind a cloud load balancer or proxy, configure NGINX Ingress Controller to use
X-Forwarded-Foror PROXY protocol so it sees the real client IP. - Use specific CIDR ranges. A
/8covers millions of IPs; prefer/24or/32in 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
/32for 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:


