The foundational Kubernetes network policy best practice is to start from a default-deny posture and then explicitly permit only the specific traffic each workload requires, rather than leaving the cluster's wide-open default in place. By default, every pod in a Kubernetes cluster can talk to every other pod. That flat network means a single compromised container can reach your database, your internal APIs, and every other namespace with nothing standing in the way. A well-designed Kubernetes network policy converts that flat network into a set of least-privilege segments, which is the single highest-leverage control you can add for lateral-movement containment.
This guide covers the practices that make network policies effective in production, and the operational traps that make teams give up on them.
First, confirm your CNI actually enforces policies
A NetworkPolicy object is only a request. Whether it does anything depends on your Container Network Interface (CNI) plugin. Some CNIs enforce policies; others silently ignore them. If you apply a default-deny policy and traffic still flows, this is almost always why.
CNIs that enforce network policy include Calico, Cilium, and others. The default kubenet and some cloud-managed configurations do not enforce policy on their own. Before you design anything, verify enforcement with a quick test: apply a deny-all policy in a test namespace and confirm that a pod there can no longer reach the internet or other pods. If it still can, your CNI is not enforcing, and every policy you write afterward is decoration.
Default-deny, then allow deliberately
The core pattern is two layers: a baseline that denies everything, and targeted policies that open exactly the paths you need.
Apply a default-deny for both ingress and egress in each namespace:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: payments
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
An empty podSelector selects every pod in the namespace, and declaring both policy types with no rules denies all ingress and egress. From there, add allow policies. For example, letting the api pods receive traffic only from the frontend pods:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-api
namespace: payments
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
Because network policies are additive, you never need a "deny" rule for specific traffic. Anything you do not explicitly allow stays blocked by the baseline. That additive model is what keeps the system comprehensible as it grows.
Do not forget egress and DNS
Teams that lock down ingress and stop there miss half the value. Egress control is what stops a compromised pod from exfiltrating data or calling out to a command-and-control host. Restrict egress to only the destinations a workload legitimately needs.
The classic gotcha: when you enable default-deny egress, DNS breaks, because pods can no longer reach kube-dns / CoreDNS. Almost every workload needs name resolution, so add an explicit egress allow for DNS to the kube-system namespace on UDP and TCP port 53. Forgetting this makes it look like your policies broke the application, when really they broke DNS.
Namespace isolation and labeling discipline
Network policies select pods and namespaces by label, so your labeling discipline is your security boundary. Two practices follow from that:
- Use namespaces as blast-radius boundaries. Put workloads of different sensitivity in different namespaces and default-deny cross-namespace traffic, then allow specific flows using
namespaceSelector. - Label namespaces reliably. Cross-namespace rules match on namespace labels, so an unlabeled or mislabeled namespace can silently widen or break access. Enforce required labels with admission policy so nobody creates an unlabeled namespace.
Because a mislabeled pod can accidentally fall outside a restrictive policy (or inside a permissive one), treat label changes on production workloads as security-relevant changes, reviewed like any other.
Test, observe, and keep policies in version control
Network policies are easy to get subtly wrong, and the failure mode is an outage. Reduce that risk:
Keep every policy in Git alongside the workloads it protects, and apply it through the same pipeline. A network policy that exists only as a live cluster object is one kubectl delete away from gone with no record.
Validate policies before rollout. Some CNIs, Cilium among them, provide connectivity-testing and policy-simulation tooling so you can confirm a policy does what you intend before it reaches production. Roll out default-deny to a canary namespace first.
Observe live traffic. Flow logs and policy-hit metrics tell you which connections are being allowed and denied, which is how you find the rule you forgot and the traffic you did not expect.
Network policy is one layer. Pair it with image scanning and admission control so you are not only isolating workloads but also keeping vulnerable images out in the first place. Our SCA product covers the container-image side of that, and the academy has a Kubernetes hardening track that ties network policy together with pod security and RBAC.
FAQ
What is the most important Kubernetes network policy best practice?
Default-deny. Kubernetes lets all pods talk to each other by default, so the highest-value step is applying a policy that denies all ingress and egress, then adding narrow allow rules for the specific traffic each workload needs. This limits how far an attacker can move after compromising one pod.
Why is my Kubernetes network policy not working?
Most often because your CNI plugin does not enforce network policy. A NetworkPolicy object has no effect unless an enforcing CNI such as Calico or Cilium is installed. Test by applying a deny-all policy and confirming traffic is actually blocked; if it is not, your CNI is the issue.
Does a default-deny egress policy break DNS?
Yes, unless you explicitly allow it. When you deny all egress, pods can no longer reach CoreDNS, so name resolution fails and applications appear broken. Add an egress rule permitting traffic to the kube-system namespace on port 53 over UDP and TCP.
How do network policies isolate namespaces?
They select traffic by pod and namespace labels. To isolate namespaces, default-deny cross-namespace traffic and then allow specific flows using a namespaceSelector. This makes reliable namespace labeling a security control, so enforce required labels through admission policy.