A Kubernetes network policy (the NetworkPolicy API object) is a namespaced resource that controls which pods can send traffic to, and receive traffic from, other pods, namespaces, and IP ranges at layers 3 and 4. By default, every pod can reach every other pod, so one compromised container can scan and pivot across namespaces. NetworkPolicy limits that lateral movement, enforces least privilege between services, and stops accidental calls, such as staging reaching production. This guide is current as of October 2026 and covers evaluation, YAML for DNS, databases and cloud metadata, testing, troubleshooting, and safe rollout.
Key takeaways
- ✓NetworkPolicy only works when the cluster’s CNI plugin enforces it, such as Calico, Cilium, or a managed equivalent that has been switched on.
- ✓A pod is isolated in a given direction only when at least one policy selects it and lists that direction in policyTypes.
- ✓Policies are additive allow-lists, so evaluation order does not matter and traffic permitted by any matching policy is allowed.
- ✓Every egress lockdown needs an explicit DNS allow on UDP and TCP port 53 to CoreDNS, or name resolution fails across the namespace.
How Kubernetes network policy works
A policy selects pods by label and attaches allow rules for ingress (incoming) and egress (outgoing) traffic, which the CNI plugin turns into packet filters. The API server stores policies but does not enforce them. On plain Flannel, policies apply with no effect. Calico, Cilium, Antrea, and Weave Net enforce them, and managed services usually need enforcement switched on:
- EKS: the network policy option in the Amazon VPC CNI
- GKE: network policy enforcement or Dataplane V2
- AKS: Azure NPM, Calico, or Cilium
The four building blocks
- podSelector: the pods in the policy’s namespace it applies to.
{}selects all of them. - policyTypes:
Ingress,Egress, or both. If you omit it, Ingress is assumed, and Egress is added only when egress rules exist. - ingress.from / egress.to: allowed peers, defined with
podSelector,namespaceSelector, oripBlock. - ports: optional protocol (TCP, UDP, SCTP) and port restrictions.
Where you place a selector changes what a rule means. When namespaceSelector and podSelector sit in the same list element, both must match (AND), giving “pods labelled X in namespaces labelled Y”. In separate elements, either can match (OR), so one misplaced dash can open a rule to every pod in a namespace. A podSelector on its own matches pods in the policy’s namespace only.
| Policies selecting the pod | Ingress | Egress |
|---|---|---|
| None | All allowed | All allowed |
| At least one with policyTypes: Ingress | Only listed sources | All allowed |
| At least one with policyTypes: Egress | All allowed | Only listed destinations |
| Both directions covered | Only listed sources | Only listed destinations |
The standard API has no deny rules, so you select pods to isolate them and then add allows. This is microsegmentation in Kubernetes environments written as YAML.
Kubernetes network policy examples
Start from a default deny and add a narrow allow for each real dependency. All examples use a namespace called shop. More patterns live in the community NetworkPolicy example repository.
Default deny ingress and egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: shop
spec:
podSelector: {}
policyTypes:
- Ingress
- EgressThe empty podSelector selects every pod in shop, and with no rules listed, nothing is allowed. Many teams apply an ingress-only version first, because egress deny breaks DNS and outbound calls until allows exist.
Allow DNS egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: shop
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53This AND selector allows only CoreDNS pods in kube-system on port 53. TCP matters because large responses fall back to TCP. Kubernetes sets the kubernetes.io/metadata.name label on every namespace automatically.
Allow frontend to backend on one port
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend-8080
namespace: shop
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080Allow external database egress and block cloud metadata
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-to-db
namespace: shop
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.20.0.0/24
ports:
- protocol: TCP
port: 5432Only this managed PostgreSQL subnet is allowed, so 169.254.169.254 stays blocked. That address is the instance metadata service, which attackers use to steal node credentials. For broad internet egress, use cidr: 0.0.0.0/0 with except: [169.254.169.254/32]. API server egress works the same way. Add an ipBlock for its endpoint IP on 443 or 6443.
What each policy allows and blocks
| Policy | Allows | Blocks | Does not affect |
|---|---|---|---|
| default-deny-all | Nothing | All pod ingress and egress in shop |
Other namespaces; CNI-exempt traffic such as kubelet health probes on most plugins |
| allow-dns-egress | DNS lookups to CoreDNS | Egress not allowed elsewhere | Ingress |
| allow-frontend-to-backend-8080 | app=frontend pods to backend on TCP 8080 |
Other pods, namespaces, and ports | Frontend egress, which needs its own allow under default deny |
| allow-backend-to-db | Backend to 10.20.0.0/24 on TCP 5432 | Other egress, including cloud metadata | Ingress |
Kubernetes network policy tutorial: apply, verify, and migrate
To create and apply a policy, write the YAML, apply it with kubectl, and prove the result with real connections, following the flow in the CKS network policy lesson:
- Confirm labels:
kubectl get pods -n shop --show-labelsandkubectl get ns --show-labels. - Apply the policy:
kubectl apply -f allow-frontend-to-backend.yaml. - Inspect it:
kubectl describe networkpolicy allow-frontend-to-backend-8080 -n shop. - Test the allowed path:
kubectl run t --rm -it -n shop --labels app=frontend --image=nicolaka/netshoot -- curl -m 3 backend:8080. - Test a blocked path from a pod with another label:
nc -zv -w 3 backend 8080should time out. - After egress changes, run
nslookup kubernetes.defaultinside a pod.
Migrating a permissive namespace: take, for example, a payments namespace with 12 deployments. Record actual flows for about 7 days with Hubble, Calico flow logs, or runtime telemetry. Apply the allow policies, which change nothing on their own. Then add ingress default deny and watch drop logs for 48 hours. Add egress deny with DNS, database, and observability allows last.
Troubleshooting Kubernetes network policy
Most failures come from a CNI that is not enforcing policies, mismatched labels, or a missing DNS allow. Check in this order:
- Enforcement: apply a deny-all in a test namespace. If traffic still flows, enforcement is off.
- Labels: a typo such as
app: front-endmakes a policy select nothing. - policyTypes: an egress policy without
Egresslisted may be read as ingress-only. - DNS: if a service name fails but its IP works, the port 53 allow is missing.
- Selectors: check the AND/OR dashes in
fromandto. - ipBlock: use it for cluster-external IPs, because pod IPs change and load balancers can rewrite source IPs.
- Packets: run
tcpdumpin a netshoot pod, orhubble observe --verdict DROPPEDon Cilium.
Best practices for production Kubernetes network policy
Production clusters work best with a default-deny baseline and small, named, version-controlled allows:
- ✓Apply default deny to every application namespace, ingress first and egress second.
- ✓Scope each allow to one source, one destination, and specific ports, named
allow-<source>-to-<dest>-<port>. - ✓Allow DNS and observability explicitly: Prometheus scraping, log shippers, and the ingress controller’s namespace.
- ✓Enforce label hygiene with admission policies so selectors stay reliable.
- ✓Store policies in Git, deploy through GitOps, and test in CI with
kubectl apply --dry-run=serverand test pods.
Isolate whole namespaces (podSelector: {} as the ingress peer) when they map to tenants or environments. Select individual pods when one namespace mixes a payment API with low-risk workloads.
Where does NetworkPolicy stop? It works at L3/L4 only. The standard API has no HTTP path filtering, workload identity, or FQDN matching. CiliumNetworkPolicy and Calico add FQDN and L7 rules, and a service mesh adds mTLS. Treat policies as one layer of Kubernetes security best practices.
How Upwind supports Kubernetes network segmentation
Upwind shows which workloads actually run and which are actually exposed, so you know which namespaces to lock down first. Its lightweight eBPF sensors collect runtime evidence across Kubernetes, APIs, and cloud workloads, so NetworkPolicy work connects to your wider cloud network security posture. Teams that only need to lint NetworkPolicy YAML for a single cluster may find a full CNAPP more than they need.
- ✓Shows which vulnerabilities are loaded and reachable inside running pods.
- ✓Detects and responds when traffic patterns turn into active threats.
- ✓Agentic Pack AI agents investigate threats, validate exposure, and generate fixes.
A solid Kubernetes network policy setup combines default deny, explicit DNS and dependency allows, verified labels, and tested rollouts. Together they turn a flat cluster network into segments that contain a breach.
FAQ
What is a Kubernetes NetworkPolicy?
A Kubernetes NetworkPolicy is a namespaced API object that controls which pods can send traffic to and receive traffic from other pods, namespaces, and IP ranges at layers 3 and 4. It is used to reduce lateral movement and enforce least-privilege communication between services.
Does Kubernetes NetworkPolicy work automatically in every cluster?
No. NetworkPolicy only works when the cluster’s CNI plugin enforces it. Tools such as Calico, Cilium, Antrea, and Weave Net can enforce policies, while plain Flannel stores the policies without applying any traffic controls.
Why does DNS often break after adding an egress default deny policy?
DNS breaks because egress lockdown also blocks name resolution unless you explicitly allow traffic to CoreDNS on UDP and TCP port 53. Without that rule, service names fail even if the destination IP would otherwise be reachable.
How are multiple Kubernetes network policies evaluated?
Kubernetes network policies are additive allow-lists. Order does not matter. If any matching policy allows a connection, that traffic is allowed. A pod becomes isolated in a direction only when at least one policy selects it and includes that direction in policyTypes.
What can’t the standard Kubernetes NetworkPolicy API do?
The standard NetworkPolicy API works only at layers 3 and 4. It does not provide deny rules, HTTP path filtering, workload identity, or FQDN matching. For FQDN or layer 7 controls, teams typically use extensions such as CiliumNetworkPolicy or Calico, or add a service mesh for mTLS.
