
By default, every pod in your Kubernetes cluster can talk to every other pod. No restrictions. No firewall. If your frontend pod can freely open a TCP connection to your database pod, so can any other compromised workload sitting in the same cluster.
That’s not a theoretical risk — it’s the blast radius problem. A single exploited pod shouldn’t become a pivot point into your entire cluster. Network Policies are how you stop that.
This guide covers Kubernetes Network Policies from scratch: what they are, how to write them, and a set of real-world rules you can deploy today.
What Is a Kubernetes Network Policy?
A NetworkPolicy is a Kubernetes resource that controls which pods can communicate with which other pods — and which external endpoints pods can reach. Think of it as a firewall rule set, but expressed as YAML and enforced at the pod level.
Without any NetworkPolicy objects in your cluster, all pod-to-pod traffic is allowed in every direction. The moment you create a NetworkPolicy that selects a pod, that pod becomes isolated — only the traffic explicitly permitted by the policy is allowed. Everything else is dropped.
Important: NetworkPolicy is enforced by your cluster’s CNI (Container Network Interface) plugin, not by Kubernetes core. You need a CNI that supports it. The major ones that do: Calico, Cilium, Weave Net, and Antrea. The popular flannel does NOT support NetworkPolicy by default. If you’re on a managed Kubernetes service — EKS, GKE, AKS, DigitalOcean Kubernetes — network policy support is either built in or available as an add-on.

The NetworkPolicy Object — Key Concepts
Before writing any YAML, understand three things:
1. Pod selection via labels Policies select pods using podSelector with label matchers. If podSelector is empty ({}), the policy applies to all pods in the namespace.
2. Policy types: Ingress and Egress
Ingressrules control incoming traffic to the selected podsEgressrules control outgoing traffic from the selected pods- A policy can define both, or just one
3. Default-deny vs allow Creating a NetworkPolicy that selects a pod immediately isolates it — any traffic not explicitly permitted is dropped. There’s no “deny” rule to write; isolation is the default the moment a policy exists for that pod.
Your First NetworkPolicy — Default Deny All
The best starting point for any namespace is a blanket deny-all policy. This is your zero-trust baseline — nothing gets in or out unless you explicitly permit it.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # selects ALL pods in this namespace
policyTypes:
- Ingress
- Egress
Deploy this first. Then add allow rules for the traffic you actually need. Start strict, open deliberately.
Allowing Specific Traffic — Real Examples
Example 1: Allow Frontend → Backend Only
Your frontend pods should reach your backend API. The backend should not be reachable from anything else in the cluster.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: backend # This policy applies to backend pods
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend # Only allow traffic FROM frontend pods
ports:
- protocol: TCP
port: 8080
Now your backend only accepts connections from pods labelled app: frontend on port 8080. Everything else — including other backend pods, monitoring agents, or a compromised sidecar — gets dropped.
Example 2: Allow Backend → Database Only
The database should only accept connections from the backend. Not from the frontend. Not from a rogue debug pod someone spun up.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-to-database
namespace: production
spec:
podSelector:
matchLabels:
app: database # Applies to database pods
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend # Only backend pods can connect
ports:
- protocol: TCP
port: 5432 # PostgreSQL port
Example 3: Allow DNS Egress (Critical — Don’t Forget This)
When you deploy a default-deny-all egress policy, you’ll immediately break DNS lookups because pods can no longer reach kube-dns. Every service discovery lookup fails. Add this alongside any egress deny policy:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: production
spec:
podSelector: {} # All pods need DNS
policyTypes:
- Egress
egress:
- ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
This is the most commonly missed step when teams first deploy network policies and then wonder why everything is broken.
Example 4: Cross-Namespace Traffic
Real clusters have multiple namespaces. Your monitoring namespace needs to scrape metrics from the production namespace. Here’s how to allow it:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-prometheus-scrape
namespace: production
spec:
podSelector:
matchLabels:
app: backend # Allow scraping of backend pods
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring # From the monitoring namespace
podSelector:
matchLabels:
app: prometheus # Specifically Prometheus pods
ports:
- protocol: TCP
port: 9090 # Metrics port
Note the indentation: namespaceSelector and podSelector are at the same level under from. This means BOTH conditions must match (the pod must be in the monitoring namespace AND be labelled app: prometheus). If you put them as separate list items under from, it becomes an OR — either condition satisfies the rule. This is a subtle but critical distinction.
# AND (both must match — same list item):
from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
podSelector:
matchLabels:
app: prometheus
# OR (either satisfies the rule — separate list items):
from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
- podSelector:
matchLabels:
app: prometheus
This AND vs OR behavior trips up almost everyone the first time.
A Complete Production-Grade Example
Here’s how you’d secure a typical three-tier app (frontend, backend, database) in a single namespace with tight network policies.
# 1. Default deny everything
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# 2. Allow DNS for all pods
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# 3. Allow ingress to frontend from internet (via ingress controller)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-to-frontend
namespace: production
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
---
# 4. Allow frontend to call backend API
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 8080
---
# 5. Allow backend to receive from frontend
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-ingress
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
---
# 6. Allow backend to reach database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-to-db
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
---
# 7. Allow database to receive from backend only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-db-ingress
namespace: production
spec:
podSelector:
matchLabels:
app: database
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 5432
Apply all of these and your three-tier app has a locked-down network topology: traffic can only flow frontend → backend → database, and nothing else.
How to Test and Debug Network Policies
Deploying a network policy blindly is a recipe for a production outage. Test before you enforce.
Test connectivity with a debug pod:
# Spin up a debug pod in the same namespace
kubectl run debug --image=nicolaka/netshoot -n production --rm -it -- bash
# From inside the pod, test connectivity to backend
curl http://backend-service:8080/health
# Test if the database port is reachable from a pod that shouldn't have access
nc -zv database-service 5432
Check which policies apply to a pod:
# List all NetworkPolicy objects in a namespace
kubectl get networkpolicy -n production
# Describe a specific policy
kubectl describe networkpolicy allow-backend-to-db -n production
Label verification — confirm your pods have the right labels:
kubectl get pods -n production --show-labels
If your policy selects app: backend but your pods are labelled app: api, the policy silently does nothing. Always verify labels match.
Cilium-specific — use hubble for live traffic visibility:
# If you're running Cilium with Hubble enabled
hubble observe --namespace production --follow
Hubble gives you a live feed of which connections are allowed and dropped — invaluable when debugging why something isn’t connecting.
Common Mistakes
1. Forgetting DNS egress after a deny-all The most common. Apply default-deny-all egress, everything breaks immediately. Always add the DNS allow rule in the same deployment.
2. AND vs OR confusion in from rules namespaceSelector and podSelector under the same list item = AND. Under separate list items = OR. Get this wrong and you either block too much or allow too much.
3. Using NetworkPolicy with a CNI that doesn’t support it If your CNI is flannel and you’re deploying NetworkPolicy objects, nothing happens. They get created successfully (Kubernetes accepts them) but they’re never enforced. You’ll think you’re secure and you’re not. Verify your CNI supports NetworkPolicy before relying on it.
4. Only writing ingress rules and forgetting egress Traffic flows both ways. A backend pod with an ingress rule that allows traffic from frontend — but no egress rule — can’t send its response back if you have a default-deny egress. You need both sides.
5. Not testing after applying Always run a connectivity test after deploying a new policy. A misconfigured policy can silently drop traffic with no error messages beyond a connection timeout.
Best Practices
- Start with default-deny-all in every namespace. Add allow rules deliberately.
- Use labels consistently — NetworkPolicy is only as good as your pod labelling discipline.
- One policy per concern — don’t pile all your rules into one massive policy. Separate policies are easier to read, debug, and update.
- Always include DNS egress when you use egress policies.
- Namespace your policies — always set the namespace explicitly in the metadata. Don’t rely on
kubectl apply -fcontext. - Keep policies in Git — treat them like any other infrastructure config. Review changes. Audit history.
- Test in staging first — never deploy a network policy change directly to production without validating in a lower environment.
What’s Next
Network Policies give you network-level isolation, but they’re one layer of a complete Kubernetes security posture. The natural next steps are:
- Pod Security Admission — control what containers can do at runtime (privilege escalation, running as root, host path mounts)
- Secrets management — locking down how pods access credentials (External Secrets Operator, HashiCorp Vault)
- Kubernetes RBAC — if you haven’t already locked down who can do what in the cluster, start with the RBAC guide
If you’re running a managed Kubernetes cluster and want to experiment with these policies without managing the control plane yourself, DigitalOcean Kubernetes supports Cilium-based network policies out of the box — worth a look for testing these patterns.
Network policies are one of the highest-impact security controls you can add to a running cluster with relatively low operational overhead. There’s no good reason to leave your pods in an open-by-default state once you understand how to lock them down.