A default Kubernetes cluster is a flat network. Every pod can reach every other pod, across every namespace, unless something tells it otherwise. That “something” is a NetworkPolicy, and most teams don’t write one until after an incident forces the question. The 2025 OWASP Kubernetes Top 10 found that missing network segmentation is one of the most common issues security teams find in production clusters, and a widely cited 2025 breach case, known as King KongTuke, showed attackers moving between cloud workloads specifically because east-west traffic controls were missing or misconfigured.
This tutorial walks through building zero-trust pod-to-pod networking on Kubernetes 1.35, the current stable release line as of September 2026. You’ll go from “anything can talk to anything” to a default-deny cluster with explicit, auditable allow rules, using either Cilium or Calico as your enforcing CNI. By the end you’ll have a working project you can drop into a test cluster today, plus a troubleshooting checklist for the mistakes that trip up almost everyone the first time.
Why Kubernetes networking is flat by default
Kubernetes ships with no network isolation out of the box. The Pod Network model guarantees that every pod gets its own IP and can reach every other pod’s IP directly, without NAT. That design makes service discovery simple, but it also means a single compromised container can, in principle, probe every other workload in the cluster: other namespaces, internal APIs, databases, and secrets stores it was never meant to touch.
This is what security researchers mean when they call a cluster “flat.” One compromised pod can reach many others if namespace or service-level segmentation is weak, and attackers who gain an initial foothold can often enumerate nearby services and expand access laterally when nothing blocks them. NetworkPolicy is the native Kubernetes object designed to close that gap, but it is inert on its own. It only works if the underlying network plugin actually reads and enforces it, which is the first thing this guide checks.
Related CVEs published in 2026 make the stakes concrete. CVE-2026-13434, a KubeVirt flaw involving Multus network attachments, shows how a network misconfiguration can create cross-namespace access and IP or MAC impersonation, and Red Hat’s advisory explicitly recommends NetworkPolicy on sensitive segments to limit lateral movement. Segmentation isn’t a nice-to-have hardening step, it’s a documented mitigation against real, published vulnerabilities.
Prerequisites and versions
Before starting, confirm you have the following. Version numbers matter here more than in most tutorials, because NetworkPolicy support and behavior differ meaningfully between CNI plugins and their releases.
- A Kubernetes cluster on version 1.33 or later. This walkthrough targets Kubernetes 1.35, the current stable release line, with patch release 1.35.8 as the latest build and 1.35.9 targeted for September 15, 2026. Kubernetes 1.35 stays in maintenance mode until December 28, 2026, with end of life on February 28, 2027, so anything you build on it has a reasonable support runway.
- kubectl matching your cluster’s minor version.
- An enforcing CNI plugin. This guide uses Cilium 1.20.1 (stable) as the primary example, with Calico v3.32.1 (released June 26, 2026) as an alternative. Both fully support the standard Kubernetes NetworkPolicy API.
- Helm 3, for installing or upgrading Cilium via its Helm chart.
- A disposable test namespace with at least three workloads (a frontend, a backend, and something representing a database) so you can observe real allow/deny behavior instead of guessing.
- A network debugging pod image such as netshoot for running curl, nc, and dig from inside the cluster.
- Fifteen minutes of downtime tolerance if you’re testing on a cluster with live traffic. Test in a non-production cluster first.
If you’re still standing up the cluster itself, our AWS EKS cluster setup walkthrough covers that step first. This guide assumes the cluster already exists and focuses purely on the network layer.
Step 1: Confirm your CNI actually enforces NetworkPolicy
This is the step almost everyone skips, and it’s the reason so many teams believe their cluster is protected when it isn’t. NetworkPolicy is just a Kubernetes API object. The API server will happily accept and store a NetworkPolicy manifest even if nothing on the data plane is capable of enforcing it. Check what you’re actually running:
kubectl get pods -n kube-system -o wide | grep -Ei 'cilium|calico|flannel|weave|antrea'
kubectl get daemonset -n kube-system
Flannel is the case that catches people out most often. Flannel is a pure overlay network. It does not implement a NetworkPolicy controller by default, meaning it will accept a NetworkPolicy object without complaint and simply never enforce it. As of Flannel v0.28.5, the plugin’s own documentation states plainly that it does not implement network policies and must be paired with Calico or another policy engine to get that behavior. Some newer distributions, like Talos 1.13 and later, can enable a Flannel network policy mode explicitly, but that is not the default anywhere.
| CNI plugin | Enforces standard NetworkPolicy? | Current version (Sept 2026) | Notes |
|---|---|---|---|
| Cilium | Yes, by default | 1.20.1 (stable) | eBPF dataplane; also supports Kubernetes ClusterNetworkPolicy (KCNP) from 1.20 onward |
| Calico | Yes, by default | v3.32.1 (June 26, 2026) | Fully supports standard NetworkPolicy plus its own extended CRD and KCNP |
| Flannel (standalone) | No | v0.28.5 | Pure overlay; requires pairing with Calico or Cilium in CNI-chaining mode to enforce policy |
| Flannel + Cilium chaining | Yes | Cilium attaches to Flannel veth interfaces | A documented workaround for clusters already committed to Flannel’s routing |
| Weave Net | Yes, by default | Maintenance mode upstream | Still functional but receives limited active development compared to Cilium/Calico |
| Antrea | Yes, by default | 2.6.1 (as shipped in one 2026 managed platform release) | Open vSwitch-based; supports Kubernetes NetworkPolicy plus its own ClusterNetworkPolicy CRD |
If your cluster is running plain Flannel with no chaining, stop here and address that before writing a single policy. Everything below assumes an enforcing CNI is already in place.
Step 2: Install or upgrade to an enforcing CNI
If step 1 showed you don’t have policy enforcement, install Cilium. Consult Cilium’s own policy documentation for the full compatibility matrix against your Kubernetes version before you commit to a rollout. On a cluster that doesn’t already have a CNI managing pod networking, or on a managed cluster where you have permission to swap the dataplane, the Helm install looks like this:
helm repo add cilium https://helm.cilium.io/
helm repo update
helm install cilium cilium/cilium --version 1.20.1 \
--namespace kube-system \
--set kubeProxyReplacement=true
# Verify the install
kubectl -n kube-system rollout status daemonset/cilium
cilium status --wait
Swapping a CNI on a running production cluster is disruptive and should be planned as a maintenance window, not run ad hoc. If you’re on a managed offering, check first whether the platform already ships Cilium or Calico as the default dataplane; GKE, EKS, and AKS all offer this as a cluster creation option, which avoids the swap entirely for new clusters.
Step 3: Build a test namespace with real workloads
Don’t write policies against a theoretical app. Deploy something with at least two tiers so you can watch traffic actually get blocked and allowed.
kubectl create namespace netpol-demo
kubectl -n netpol-demo create deployment backend --image=nginx:1.27 --replicas=1
kubectl -n netpol-demo expose deployment backend --port=80
kubectl -n netpol-demo create deployment frontend --image=nginx:1.27 --replicas=1
kubectl -n netpol-demo run netshoot --image=nicolaka/netshoot --command -- sleep infinity
Label your deployments clearly, since every rule you write from here on depends on label selectors matching correctly:
kubectl -n netpol-demo label deployment backend role=backend
kubectl -n netpol-demo label deployment frontend role=frontend
Step 4: Confirm the baseline (everything can talk to everything)
Before applying any policy, prove the flat-network problem to yourself. From the netshoot pod, hit the backend service directly:
kubectl -n netpol-demo exec -it netshoot -- curl -sS -o /dev/null -w '%{http_code}\n' backend.netpol-demo.svc.cluster.local
Expected output at this stage: 200. Any pod in any namespace, including one you didn’t deploy and don’t control, can reach that backend right now. That’s the problem this tutorial fixes.
Step 5: Apply default-deny for ingress and egress
The correct starting point for zero trust is denying everything, then adding back only what’s needed. This is the single most important manifest in this tutorial. Apply it to the namespace first:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: netpol-demo
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
An empty podSelector: {} matches every pod in the namespace. With no ingress or egress rules listed, and both policy types declared, this blocks all inbound and all outbound traffic for every pod in netpol-demo. Apply it and re-run the curl test from step 4; you should now get a timeout instead of a 200.
kubectl apply -f default-deny-all.yaml
kubectl -n netpol-demo exec -it netshoot -- curl -sS --max-time 3 backend.netpol-demo.svc.cluster.local
This is also the point where most teams discover their applications break in ways they didn’t anticipate: DNS resolution, health checks, and metrics scraping all stop working at the same time. That’s expected. The next several steps rebuild access deliberately, one path at a time.
Step 6: Restore DNS before anything else
This is the pitfall that generates the most support tickets after a default-deny rollout. Every pod resolves service names through CoreDNS, which normally lives in kube-system. Without an explicit egress rule allowing DNS traffic, pods can’t resolve anything, including service names inside their own namespace, and the failure often looks like a networking bug rather than a missing DNS rule.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: netpol-demo
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Apply this immediately after your default-deny policy, every time, on every namespace. Treat it as non-negotiable boilerplate rather than an optional extra.
Step 7: Allow frontend to reach backend, and nothing else
Now add back exactly the path your application needs. This policy attaches to the backend pods and permits ingress only from pods labeled role=frontend, on port 80:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: netpol-demo
spec:
podSelector:
matchLabels:
role: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 80
Test from the frontend pod (should succeed) and from netshoot, which has no matching label (should still fail):
# From frontend: expect 200
kubectl -n netpol-demo exec -it deploy/frontend -- curl -sS -o /dev/null -w '%{http_code}\n' backend.netpol-demo.svc.cluster.local
# From netshoot (unlabeled): expect timeout
kubectl -n netpol-demo exec -it netshoot -- curl -sS --max-time 3 backend.netpol-demo.svc.cluster.local
If the frontend request also fails, the first thing to check is a label typo. NetworkPolicy selectors fail silently: a mismatched label doesn’t throw an error anywhere, the traffic is simply dropped as if no rule existed at all.
Step 8: Isolate traffic across namespaces
Real clusters run multiple teams’ workloads in separate namespaces, and cross-namespace calls need their own explicit rule. Use namespaceSelector, matched by the built-in kubernetes.io/metadata.name label that Kubernetes attaches to every namespace automatically:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-payments-namespace
namespace: netpol-demo
spec:
podSelector:
matchLabels:
role: backend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: payments
podSelector:
matchLabels:
role: billing-worker
ports:
- protocol: TCP
port: 80
Note the nesting: combining namespaceSelector and podSelector inside the same from entry means “pods matching this label, in namespaces matching this label” (an AND condition). Listing them as two separate entries in the array instead means “either of these” (an OR condition). This is the second most common source of silently-wrong policies, right after label typos.
Step 9: Restrict egress to known external destinations
Ingress rules get most of the attention, but egress matters just as much for containing a breach. If an attacker does compromise a pod, an unrestricted egress policy lets them exfiltrate data to any external IP. Lock egress down to known CIDR ranges for anything talking to a third-party API or database outside the cluster:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-to-payment-gateway
namespace: netpol-demo
spec:
podSelector:
matchLabels:
role: backend
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 203.0.113.0/24
ports:
- protocol: TCP
port: 443
Be careful with CIDR-based egress rules on Cilium specifically. CVE-2026-56743 documents a parser bug in Cilium versions 1.19.0 through 1.19.4: standard NetworkPolicy specs using CIDR-based ipBlock rules without a pod or namespace selector, on a cluster configured with a custom clusterName, incorrectly generated a wildcard namespace allow rule instead of the intended restrictive one. If you’re running an affected version, upgrade to 1.20.1 or later before relying on ipBlock rules for anything security-sensitive.
Step 10: Verify enforcement with a debug pod, not assumptions
Every rule above should be verified with an actual connection attempt, not just a kubectl apply that returns without error. The API server accepting your YAML tells you nothing about whether the CNI enforced it.
# Should succeed (matches allow-frontend-to-backend)
kubectl -n netpol-demo exec -it deploy/frontend -- nc -zv -w 3 backend 80
# Should fail (no rule permits netshoot -> backend)
kubectl -n netpol-demo exec -it netshoot -- nc -zv -w 3 backend 80
# Should fail (egress to an IP outside any allow rule)
kubectl -n netpol-demo exec -it deploy/backend -- curl -sS --max-time 3 https://example.com
If you’re on Cilium, Hubble gives you a live, per-flow view of what’s being allowed and dropped, which is far faster than guessing from application logs:
cilium hubble port-forward &
hubble observe --namespace netpol-demo --verdict DROPPED
On Calico, the equivalent visibility comes from calicoctl policy commands and Felix logs, which show which policy object matched or dropped a given flow.
Step 11: Add a cluster-wide admin baseline with ClusterNetworkPolicy
Namespaced NetworkPolicy has one structural weakness: any user with permission to create objects in a namespace can also create a permissive policy that undoes your careful defaults. The upstream Kubernetes SIG-Network ClusterNetworkPolicy (KCNP) API, at policy.networking.k8s.io/v1alpha2, addresses this with cluster-scoped rules that namespace owners cannot override. Calico’s latest release notes describe implementing this resource with Admin and Baseline policy tiers, and Cilium has supported the same upstream cluster-scoped policy API since version 1.20, tracked in its public release history.
apiVersion: policy.networking.k8s.io/v1alpha2
kind: ClusterNetworkPolicy
metadata:
name: baseline-deny-cross-namespace
spec:
priority: 100
subject:
namespaces: {}
ingress:
- name: deny-all-cross-namespace
action: Deny
from:
- namespaces:
notSameLabels:
- kubernetes.io/metadata.name
This is a newer, evolving API, so check your specific CNI’s KCNP documentation for exact field support before relying on it for compliance requirements. Treat it as a backstop, applied by platform administrators, sitting underneath the namespaced policies application teams manage day to day.
Step 12: Roll this out as policy-as-code, not one-off YAML
Hand-applying NetworkPolicy manifests works for a demo namespace. It does not scale to dozens of teams and hundreds of services. Store every policy in version control alongside the application manifests it protects, and gate merges with a policy linter or a dry-run apply against a staging cluster. If your organization already runs admission control for other purposes, our container security hardening guide covers pairing NetworkPolicy with Kyverno and image-signing checks for defense in depth, and our Helm chart deployment tutorial shows how to template these policies alongside the workloads they protect instead of maintaining them as separate, easily-forgotten files.
If a rollout does go wrong in production, mid-deploy, don’t hand-edit the policy under pressure. Roll the change back the same way you’d roll back any other manifest; our deployment rollback guide covers the exact commands for undoing a bad release cleanly.
The complete working project
Combined, the manifests above form a working project you can apply to any test cluster with an enforcing CNI. Save this as a single file and apply it; Kubernetes processes NetworkPolicy objects independently of apply order, but reading it top to bottom mirrors the steps in this tutorial:
apiVersion: v1
kind: Namespace
metadata:
name: netpol-demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: netpol-demo
labels:
role: backend
spec:
replicas: 1
selector:
matchLabels:
role: backend
template:
metadata:
labels:
role: backend
spec:
containers:
- name: backend
image: nginx:1.27
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: backend
namespace: netpol-demo
spec:
selector:
role: backend
ports:
- port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: netpol-demo
labels:
role: frontend
spec:
replicas: 1
selector:
matchLabels:
role: frontend
template:
metadata:
labels:
role: frontend
spec:
containers:
- name: frontend
image: nginx:1.27
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: netpol-demo
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: netpol-demo
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: netpol-demo
spec:
podSelector:
matchLabels:
role: backend
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 80
Apply it with kubectl apply -f netpol-project.yaml, then work through the verification commands in step 10 to confirm frontend can reach backend, netshoot cannot, and DNS still resolves cluster-wide.
NetworkPolicy field reference
Keep this next to you while writing your own rules. Every field below behaves consistently whether you’re running Cilium, Calico, or Antrea, since they all implement the same upstream API for standard NetworkPolicy.
| Field | Purpose | Common mistake |
|---|---|---|
podSelector | Chooses which pods this policy applies to | An empty {} means “all pods” — easy to apply unintentionally |
policyTypes | Declares whether the policy governs Ingress, Egress, or both | Omitting Egress when you meant to restrict outbound traffic too |
ingress.from | Lists allowed traffic sources | Combining selectors inside one list item (AND) vs. separate items (OR) |
egress.to | Lists allowed traffic destinations | Forgetting DNS egress, breaking name resolution cluster-wide |
namespaceSelector | Matches source/destination namespaces by label | Assuming namespace names are labels; you must match kubernetes.io/metadata.name |
ipBlock.cidr | Matches traffic by IP range, for external endpoints | Known Cilium 1.19.0–1.19.4 parser bug (CVE-2026-56743) mishandled unscoped ipBlock rules |
ports | Restricts the policy to specific protocol/port combinations | Leaving it out entirely allows all ports on an otherwise-restricted connection |
Common pitfalls
- Assuming Flannel enforces policy. It doesn’t, by default. Verify your CNI before writing a single rule.
- Forgetting the DNS egress rule. This single omission after a default-deny rollout breaks name resolution for every pod in the namespace, and the symptom rarely points directly at the cause.
- Applying default-deny to ingress only. Egress traffic stays wide open, which does nothing to stop data exfiltration from a compromised pod.
- Label typos. A selector that doesn’t match anything fails silently. There’s no error, no warning, just traffic that quietly stops flowing.
- Confusing AND and OR logic in selectors. Combining
namespaceSelectorandpodSelectorin one list entry is an AND; separate entries in the same array are an OR. Mixing these up produces policies that look correct but are far more, or far less, permissive than intended. - Ignoring kube-system and health checks. A namespace-wide deny can also block kubelet-driven readiness and liveness probes if those probes don’t originate from an allowed source, causing pods to be marked unhealthy for reasons that have nothing to do with the application code.
- Treating NetworkPolicy as a complete security control. It restricts network paths. It does nothing about RBAC over-permissioning, exposed secrets, or vulnerable container images. Pair it with the broader hardening steps in a dedicated container security review, not as a replacement for them.
Troubleshooting
- Policy applies with no errors but traffic still isn’t blocked. Check that your CNI enforces NetworkPolicy at all. Flannel without chaining silently ignores the object.
- All DNS lookups fail after default-deny. You’re missing the egress rule to kube-system on UDP/TCP port 53. This is the most common single cause of “everything broke” after a policy rollout.
- A specific pod can’t reach a service it used to reach. Compare the pod’s actual labels against the policy’s selector with
kubectl get pod <name> --show-labels. A mismatch here is more common than a logic error in the policy itself. - Cross-namespace calls fail even with a namespaceSelector rule. Confirm you’re matching on
kubernetes.io/metadata.name, the automatic label Kubernetes assigns, rather than assuming the namespace name itself acts as a label key. - Readiness or liveness probes start failing after applying a namespace-wide policy. Some CNIs route kubelet probe traffic differently from regular pod-to-pod traffic; check your CNI’s documentation for whether probes need an explicit allow or are exempted automatically.
- Egress rules using CIDR blocks behave inconsistently on Cilium. If you’re on Cilium 1.19.0 through 1.19.4, check CVE-2026-56743; a parser bug in that version range can generate an unintended wildcard allow. Upgrade to 1.20.1 or later.
- You can’t tell which policy is dropping a given connection. Use Hubble (Cilium) or Felix flow logs (Calico) to see per-flow verdicts instead of trial-and-error editing of YAML files.
- A policy meant to be temporary is still active months later and nobody remembers why. This is an organizational problem, not a technical one. Store policies in version control with the same review process as application code, so there’s a commit history explaining every rule.
Advanced tips
Once the basics are stable, a few refinements are worth the extra effort. Cilium’s own CiliumNetworkPolicy CRD extends the standard API with Layer 7 rules, letting you allow traffic based on HTTP method and path rather than just IP and port, which is useful when two services share a port but only some routes should be reachable from a given caller.
Layer the ClusterNetworkPolicy admin tier underneath your namespaced policies so a compromised or misconfigured application namespace can’t accidentally open a path the platform team explicitly closed. Calico’s Admin and Baseline tiers, and Cilium’s equivalent KCNP support from 1.20 onward, both exist specifically for this separation of concerns between platform and application teams.
Finally, treat NetworkPolicy as one layer of a broader zero-trust posture, not the whole thing. A service mesh with mutual TLS adds identity-based authentication on top of the IP-and-port restrictions NetworkPolicy provides, so even a pod that manages to reach an allowed destination can’t impersonate a legitimate caller. The two controls are complementary rather than redundant.
Kubernetes 1.35 networking changes worth knowing
A few upstream changes affect how you’ll write and operate policies on current Kubernetes releases. Kubernetes 1.34 introduced PreferSameZone and PreferSameNode traffic distribution as new service-routing preferences, giving operators finer control over where traffic gets routed within a cluster. Kubernetes 1.35 builds on that: the PreferSameNode policy graduated to general availability, and the older PreferClose policy was deprecated in its favor. Neither change alters NetworkPolicy semantics directly, but they do affect which node a given connection lands on, which is worth knowing if you’re debugging latency alongside policy behavior.
Separately, Kubernetes 1.34 relaxed DNS search path validation through KEP #4427, allowing underscores in domain labels and a single dot in the search path under the new validation rules. If your DNS egress policy is scoped narrowly to specific query patterns rather than simply allowing kube-system on port 53, revisit it after upgrading, since the shape of legitimate DNS traffic has shifted slightly.
Known CVEs to check before you rely on this setup
NetworkPolicy enforcement depends on your CNI’s code being correct, and CNI plugins have shipped their own vulnerabilities in 2026. Check your versions against these before treating your setup as hardened.
| CVE | Component | Issue | Fix |
|---|---|---|---|
| CVE-2026-56743 | Cilium 1.19.0–1.19.4 | CIDR-based ipBlock rules without a pod/namespace selector, on clusters with a custom clusterName, generated an unintended wildcard namespace allow | Upgrade to Cilium 1.20.1 or later |
| CVE-2026-41185 | Tigera Calico, Azure IPAM backend | Information disclosure vulnerability specific to Calico’s CNI plugin when Azure IPAM is configured | Apply the patched Calico release; avoid Azure IPAM until updated |
| CVE-2026-41184 | Calico installer (Canal/Flannel-Calico deployments) | The install-cni init container could log a live ServiceAccount bearer token when the __SERVICEACCOUNT_TOKEN__ placeholder rendered into logs | Update Calico; restrict namespace log access as a compensating control |
| CVE-2026-13434 | KubeVirt, Multus network attachments | Unvalidated network attachment names enabled cross-namespace network access and IP/MAC impersonation | Patch KubeVirt; apply NetworkPolicy on sensitive segments as a mitigation |
None of these make NetworkPolicy a bad investment. They’re a reminder that the enforcement layer is software, and software gets patched. Track the official Kubernetes CVE feed and your CNI vendor’s release notes as part of normal operations, the same way you’d track CVEs for any other piece of cluster infrastructure. Clusters running with known container-escape paths, such as the GKE Fragnesia vulnerability disclosed earlier in 2026, are exactly the scenario where network segmentation limits how far an attacker gets after the initial compromise, a point covered in more detail in our GKE Fragnesia breakdown.
Testing policies before they reach production
Treat NetworkPolicy manifests the way you’d treat any other change that can take down a service: test in a lower environment first, with the same labels and namespace structure as production. A staging cluster that mirrors your production label scheme catches selector mistakes before they reach anything customer-facing.
A simple CI check that applies every policy to a disposable kind or k3d cluster, then runs a small set of expected-allow and expected-deny connection tests, catches the two most expensive failure modes: a policy that’s more permissive than intended, and a policy that breaks a legitimate path nobody thought to test. Neither failure mode shows up in a YAML linter; both only show up when you actually try to connect.
# Minimal CI-style check: apply policies, then assert expected behavior
kubectl apply -f netpol-project.yaml
kubectl -n netpol-demo wait --for=condition=ready pod -l role=backend --timeout=60s
kubectl -n netpol-demo exec deploy/frontend -- nc -zv -w 3 backend 80 \
&& echo "PASS: frontend can reach backend" || echo "FAIL: expected allow, got deny"
kubectl -n netpol-demo exec netshoot -- nc -zv -w 3 backend 80 \
&& echo "FAIL: expected deny, got allow" || echo "PASS: netshoot correctly denied"
Run this on every pull request that touches a NetworkPolicy manifest, and you’ll catch the label-typo and selector-logic mistakes described earlier before they reach a real cluster. For broader cluster lifecycle practices, our Kubernetes EOL and support guide is worth reading alongside this one, since policy compatibility is one more reason to stay off unsupported minor versions.
Frequently asked questions
Does every Kubernetes distribution support NetworkPolicy out of the box?
No. Support depends entirely on the CNI plugin, not on Kubernetes itself. Cilium, Calico, and Antrea enforce it by default; standalone Flannel does not.
Will applying a default-deny policy break existing traffic immediately?
Yes, for any traffic that doesn’t have a matching allow rule already in place, including DNS. Apply the DNS egress rule in the same change, and expect to spend time restoring specific paths afterward.
Can I use NetworkPolicy across a multi-cluster setup?
Standard NetworkPolicy is scoped to a single cluster’s pod network. Cross-cluster traffic control requires a service mesh or a CNI’s specific multi-cluster feature, such as Cilium’s ClusterMesh.
Is NetworkPolicy alone enough to stop lateral movement?
It substantially reduces the attack surface, but it’s one layer. Pair it with least-privilege RBAC, image scanning, and runtime detection for a complete posture.
What’s the difference between NetworkPolicy and ClusterNetworkPolicy (KCNP)?
NetworkPolicy is namespaced, and anyone with permission to create objects in a namespace can add a permissive rule. ClusterNetworkPolicy is cluster-scoped and intended for platform administrators to set a baseline namespace owners cannot override.
Do NetworkPolicy rules affect performance?
eBPF-based enforcement in Cilium adds minimal overhead compared to older iptables-based approaches. If you notice latency after rolling out policies, check whether you’re on an older kube-proxy-based dataplane rather than assuming NetworkPolicy itself is the cause.
How do I know if a policy I wrote is actually too permissive?
Test the negative case, not just the positive one. Confirm traffic you expect to be denied is actually denied, using a pod that deliberately doesn’t match any allow rule, the same way this tutorial used the netshoot pod throughout.
Should I write one giant policy per namespace or many small ones?
Many small, purpose-specific policies are easier to audit and roll back individually. A single sprawling policy becomes difficult to reason about once more than a couple of services are involved.




