Write Network Policies: From L3/L4 to L7 Rules
Kubernetes starts with every Pod able to reach every other Pod — CiliumNetworkPolicy is how you narrow that down, down to the HTTP method if you need to.
By default, Kubernetes networking has no restrictions: any Pod can reach any other Pod in the cluster, on any port. For a cluster with any multi-tenancy or any security requirement beyond “trust everything internally,” that default is usually wrong. CiliumNetworkPolicy is how you narrow it, from simple allow-lists up to rules that inspect the actual HTTP request.
The starting point: everything is open
Confirm the default before changing anything:
kubectl run client --image=curlimages/curl --command -- sleep infinity
kubectl exec client -- curl -s http://some-other-service.default.svc.cluster.local
With no policies in place, that request succeeds regardless of what client is or what some-other-service does. Once any policy selects a given Pod for a given direction (ingress or egress), that Pod’s traffic in that direction becomes default-deny: only what the policy explicitly allows gets through. A Pod with no policy selecting it at all remains fully open. This is the single most important thing to understand before writing your first policy: writing one scoped too narrowly can silently cut off traffic you didn’t intend to touch, for any Pod it happens to select.
L3/L4: restrict by identity and port
The most common policy shape restricts which Pods can reach which Pods, on which ports, using Cilium’s label-based identity (covered in this series’ guide on how Cilium uses eBPF) rather than IP addresses:
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: allow-frontend-to-api
namespace: default
spec:
endpointSelector:
matchLabels:
app: api
ingress:
- fromEndpoints:
- matchLabels:
app: frontend
toPorts:
- ports:
- port: "8080"
protocol: TCP
Reading this: it applies to Pods labeled app: api (endpointSelector), and allows inbound traffic (ingress) only from Pods labeled app: frontend, only on TCP port 8080. Any other Pod, and any other port, is now denied to api Pods, since this policy’s existence puts them into default-deny for ingress.
Egress works the same way, in the other direction
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: api-egress-to-database
namespace: default
spec:
endpointSelector:
matchLabels:
app: api
egress:
- toEndpoints:
- matchLabels:
app: database
toPorts:
- ports:
- port: "5432"
protocol: TCP
api Pods can now only initiate outbound connections to database Pods on port 5432. Combine ingress and egress rules in the same policy, or keep them separate, depending on what reads more clearly for your setup; functionally, matching endpointSelectors on separate policies for the same Pods still compose together correctly.
Allowing traffic to or from outside the cluster
fromEndpoints/toEndpoints match Pods by label. For traffic to or from something outside the cluster’s Pod network, DNS-based or CIDR-based rules apply instead:
egress:
- toFQDNs:
- matchName: "api.external-service.com"
toPorts:
- ports:
- port: "443"
protocol: TCP
toFQDNs is a Cilium-specific extension beyond plain Kubernetes NetworkPolicy, letting you allow egress to a named external domain rather than needing to know and pin its IP addresses in advance, which matters for any external service whose IPs you don’t control and that may change without notice.
L7: policy that reads the actual request
Everything above operates at L3/L4: identities and ports, without looking inside the traffic. Cilium can go further and make decisions based on the content of L7 protocols it understands, HTTP being the most common:
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: api-read-only-for-reporting
namespace: default
spec:
endpointSelector:
matchLabels:
app: api
ingress:
- fromEndpoints:
- matchLabels:
app: reporting
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: "GET"
path: "/reports/.*"
This permits the reporting service to reach api on port 8080, but only GET requests whose path matches /reports/.*. A POST to the same port, or a GET to an unlisted path, is denied, even though it’s the same Pods on the same port that L3/L4 rules alone would have allowed wholesale. This is enforced by Cilium’s embedded Envoy proxy for the Pods the rule applies to; L7 policy has a real but generally modest performance cost compared to pure L3/L4 eBPF enforcement, worth keeping in mind for very high-throughput paths where L3/L4 restriction is sufficient for your actual security requirement.
Test before you trust
A policy applied incorrectly fails closed, denying traffic, not open, which is the safer failure mode but still an outage if it’s the wrong traffic. Before applying a new restrictive policy to anything serving real traffic:
kubectl apply --dry-run=server -f policy.yaml
Then apply it in a lower environment first, generate traffic that should be allowed and traffic that should be denied, and confirm both behave as expected, before promoting the same policy to anything production serves.
Start from allow-all, then narrow deliberately
A reasonable adoption path for a cluster with no existing policies: don’t try to write a complete, correct policy set on day one. Start by observing real traffic patterns (Hubble, covered in this technology’s landing page, is built for exactly this), write policies that match what’s actually observed to be legitimate, apply them in a non-production environment first, and only then narrow production traffic down to match. Writing restrictive policy against guessed traffic patterns, rather than observed ones, is a common way to cause an outage that looks like a Cilium problem but is actually a policy that didn’t account for a legitimate pattern nobody thought to list.
Popular Guides
-
How Cilium Uses eBPF: Identity, Policy, and Sockets What actually happens in the kernel when a Pod starts, talks to another Pod, or gets a packet policy decision — and why it's fast. -
Set Up a Cluster for Cilium: Disable the Default CNI First Cilium replaces your cluster's networking entirely, which means the order of operations matters more than with most add-ons. -
Expose Services with Cilium's Gateway API Support GatewayClass, Gateway, and HTTPRoute — host-based routing, path matching, URL rewrites, and TLS, with no separate gateway Pods.