Kubernetes NetworkPolicy Default Deny Egress
Implement Kubernetes NetworkPolicy default deny egress rules. Block all outbound traffic, then allow specific destinations: DNS, external APIs.
π‘ Quick Answer: Apply a NetworkPolicy with
policyTypes: ["Egress"]and emptyegress: []to deny all outbound traffic. Then create additional policies to allow DNS (port 53), specific services, and required external endpoints. Always allow DNS first or nothing works.
The Problem
By default, pods can reach any destination β internal or external:
- Compromised pods can exfiltrate data
- Lateral movement between namespaces is unrestricted
- No visibility into what pods are connecting to
- Compliance requires explicit egress allowlists
The Solution
Default Deny All Egress
# Deny ALL outbound traffic for all pods in namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
namespace: production
spec:
podSelector: {} # Applies to ALL pods in namespace
policyTypes:
- Egress
egress: [] # Empty = deny allAllow DNS (Always Needed First)
# Without DNS, pods can't resolve any service names
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {} # All pods
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: 53Allow Internal Service Communication
# Allow app pods to reach database in same namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app-to-database
namespace: production
spec:
podSelector:
matchLabels:
app: myapp
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
---
# Allow app to reach Redis in another namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app-to-redis
namespace: production
spec:
podSelector:
matchLabels:
app: myapp
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: cache
podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379Allow Specific External IPs
# Allow outbound to specific external API
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-external-api
namespace: production
spec:
podSelector:
matchLabels:
app: payment-service
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 203.0.113.0/24 # External payment API range
ports:
- protocol: TCP
port: 443
# Also allow DNS for resolution
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53Complete Zero-Trust Namespace Setup
# 1. Deny all ingress AND egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
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:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# 3. Allow ingress from ingress controller
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-controller
namespace: production
spec:
podSelector:
matchLabels:
app: myapp
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 8080Architecture
graph TD
A[Pod in Production NS] -->|default deny| B[β All Egress Blocked]
A -->|allow-dns policy| C[β
CoreDNS :53]
A -->|app-to-database| D[β
Postgres :5432]
A -->|allow-external-api| E[β
Payment API :443]
A -->|no policy| F[β Internet]
A -->|no policy| G[β Other namespaces]Common Issues
| Issue | Cause | Fix |
|---|---|---|
| All DNS resolution breaks | Forgot to allow DNS egress | Add DNS allow policy first |
| Pods can still reach everything | CNI doesnβt support NetworkPolicy | Use Calico, Cilium, or Antrea |
| Cross-namespace traffic blocked | Missing namespaceSelector | Add namespace label selector |
| External API unreachable | Need both DNS + IP block allow | Add both in same policy |
| Health checks fail | Liveness probe from kubelet blocked | kubelet isnβt subject to NetworkPolicy (host network) |
Best Practices
- Apply deny-all FIRST, then allow specific β explicit allowlist approach
- Always allow DNS β first policy after deny-all, or nothing resolves
- Label namespaces β
kubernetes.io/metadata.nameis auto-applied since K8s 1.21 - Test with
kubectl exec curlβ verify connectivity after applying policies - Use Cilium/Calico for FQDN egress rules β native NetworkPolicy only supports IPs
Key Takeaways
policyTypes: ["Egress"]with emptyegress: []= deny all outbound- DNS (UDP/TCP 53 to kube-system) must be explicitly allowed or nothing works
- Policies are additive β multiple policies allowing different things all take effect
- CNI must support NetworkPolicy (Calico, Cilium, Antrea) β kubenet/flannel donβt
- Host-networked processes (kubelet probes) bypass NetworkPolicy β no need to allow

Recommended
Kubernetes Recipes β The Complete Book100+ production-ready patterns with detailed explanations, best practices, and copy-paste YAML. Everything in one place.
Get the Book βLearn by Doing
CopyPasteLearn β Hands-on Cloud & DevOps CoursesMaster Kubernetes, Ansible, Terraform, and MLOps with interactive, copy-paste-run lessons. Start free.
Browse Courses βπ Deepen Your Skills β Hands-on Courses
Courses by CopyPasteLearn.com β Learn IT by Doing
