Kubernetes network policies: default deny and allowing only needed traffic
In short: By default every pod in a Kubernetes cluster can talk to every other pod. Network policies, enforced by the cluster network plugin, let you deny all traffic and then allow only named flows by label, namespace and port. This contains attackers and is a core part of zero trust in a cluster.
The default is wide open
Kubernetes gives every pod an IP address and, out of the box, lets any pod connect to any other pod in any namespace. If an attacker compromises one web pod, they can scan and reach databases, internal APIs and monitoring tools across the cluster. A NetworkPolicy is a Kubernetes object that restricts this, using labels to select pods.
Policies are enforced by the Container Network Interface (CNI) plugin, not by Kubernetes itself. Calico, Cilium and most managed-cloud network options support them, but a few basic plugins do not. If your CNI ignores policies, applying them silently does nothing, so always test.
How policies work
- A policy selects pods with
podSelector. Once any policy selects a pod for ingress (or egress), that direction becomes deny-by-default for that pod, and only listed traffic is allowed. - Rules can allow traffic from or to other pods (
podSelector), namespaces (namespaceSelector) or IP ranges (ipBlock), on given ports. - Policies are additive: if several apply, the union of their allows is permitted.
- Standard NetworkPolicy works at layers 3 and 4. Some CNIs add layer 7 rules, DNS-name rules or cluster-wide policies through their own resources.
Default deny, then allow
Start each application namespace with a policy that denies all ingress and egress:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: shop
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
Denying egress also blocks DNS, so almost every namespace needs a rule allowing UDP and TCP port 53 to the cluster DNS pods. Then add specific allows, such as letting the API pods reach the database on its port:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-allow-api
namespace: shop
spec:
podSelector:
matchLabels: { app: db }
policyTypes: ["Ingress"]
ingress:
- from:
- podSelector:
matchLabels: { app: api }
ports:
- protocol: TCP
port: 5432
Designing policies
- Map the real flows for each app, using CNI flow logs or observability tools where available.
- Label pods consistently (for example
appandtier), because policies depend on labels. - Apply default deny per namespace, plus a DNS allow.
- Add allow rules tier by tier and test each one.
- Restrict egress to the internet, and block the cloud metadata address (169.254.169.254) for pods that do not need it.
- Keep policies in git and review them like code.
| Common mistake | Effect |
|---|---|
| Forgetting DNS after egress deny | Every app breaks with name resolution errors |
| CNI does not enforce policies | False sense of security |
| Allowing whole namespaces | Any compromised pod there gets access |
| Labels anyone can change | Attackers relabel pods to match allow rules |
Tip: this is the cluster version of the segmentation ideas in zero trust segmentation: deny by default and allow by identity, not by network location.
Practice
You apply a default-deny egress policy and every app fails to resolve names. Why?
What enforces Kubernetes network policies?
What does an empty podSelector ({}) select?
Key takeaways
- Kubernetes allows all pod-to-pod traffic by default.
- Network policies select pods by label and allow listed flows.
- Start with default deny for ingress and egress, plus DNS.
- Enforcement depends on the CNI; test that it works.
- Restrict egress and access to the cloud metadata service.
Original content written by Mohamed Fayas for FM TecHub. © 2026 FM TecHub. You're welcome to share a link to this page; please don't copy the text elsewhere without permission.