Problem
The API gateway service (gateway-prod) suddenly stopped receiving inbound HTTP requests after a routine NetworkPolicy update. The symptom manifested as 5xx errors from downstream services and connection timeouts observed by clients.
Observed logs
2026-10-05T14:32:10.123Z kubelet[1234]: E0905 14:32:10.123456 1234 network_plugin.go:215] Failed to program iptables rules: iptables-restore v1.8.4: line 23: Invalid argument
2026-10-05T14:32:10.124Z kube-proxy[5678]: I0905 14:32:10.124567 5678 proxy.go:123] Service "gateway-prod" has no endpoints
2026-10-05T14:32:12.001Z calico-node[2345]: 2026-10-05 14:32:12.001 [ERR] Policy "allow-gateway-ingress" denied traffic from pod "frontend-7c9d9f5b5c-9k2lm" to service "gateway-prod"
Impact:
- Client requests time out (average latency ↑ from 30 ms to >30 s).
- Downstream micro‑services report “connection refused”.
- Auto‑scaling metrics for the gateway show zero traffic, causing scale‑down events.
Root Cause
The updated NetworkPolicy allow-gateway-ingress unintentionally restricted both inbound traffic to the gateway pods and outbound traffic from the gateway to other services. The policy was intended to allow traffic only from the frontend namespace, but the selector logic used podSelector: {} in the ingress rule, which matches *all* pods in the namespace, and the egress rule omitted the required to clause, resulting in a default deny for all egress.
Key misconfiguration details:
- Ingress rule used
namespaceSelector: {matchLabels: {name: frontend}}with an emptypodSelector, causing the policy to apply to *every* pod in thefrontendnamespace, including the gateway itself, which then blocked external traffic that does not originate from a pod (e.g., LoadBalancer IP). - Egress rule was defined without a
toselector, so the policy fell back to “deny all egress” for the gateway pods, preventing them from reaching downstream services (databases, auth services, etc.). - The policy was applied with
policyTypes: [Ingress, Egress]but the intention was to control only ingress.
Debug
Step‑by‑step investigation that led to the root cause:
- Identify affected pods
kubectl get pods -n prod -l app=gateway-prod -o wide
All gateway pods reported Ready status but had IPTABLES drops in the CNI logs.
- Inspect the NetworkPolicy
kubectl get networkpolicy allow-gateway-ingress -n prod -o yaml
Output (truncated):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-gateway-ingress
namespace: prod
spec:
podSelector:
matchLabels:
app: gateway-prod
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: frontend
podSelector: {}
egress: []
The empty podSelector under from matches all pods in frontend, and the empty egress list translates to “deny all egress”.
- Check CNI logs for drops
kubectl logs -n kube-system -l k8s-app=calico-node -c calico-node --tail=100 | grep deny
Sample log entry:
2026-10-05 14:32:12.001 [ERR] Policy "allow-gateway-ingress" denied traffic from pod "frontend-7c9d9f5b5c-9k2lm" (10.244.2.5) to pod "gateway-prod-6d9f7c8d9b-2xv9k" (10.244.3.12) on port 80
- Validate iptables rules generated by the policy
kubectl exec -n prod -c calico-node $(kubectl get pods -n kube-system -l k8s-app=calico-node -o name | head -n1) -- iptables -S | grep gateway-prod
Result showed a -A INPUT -s 10.244.2.0/24 -d 10.244.3.12 -j DROP rule.
Solution
Rewrite the NetworkPolicy to explicitly allow inbound traffic from the frontend namespace on the required ports and to permit egress to all services needed by the gateway.
Before (faulty policy)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-gateway-ingress
namespace: prod
spec:
podSelector:
matchLabels:
app: gateway-prod
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: frontend
podSelector: {}
egress: []
After (corrected policy)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-gateway-ingress
namespace: prod
spec:
podSelector:
matchLabels:
app: gateway-prod
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: frontend
podSelector:
matchLabels:
role: client
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
- protocol: TCP
port: 5432 # example DB port
- protocol: TCP
port: 6379 # example Redis port
Key changes:
- Added explicit
podSelectorunderfromto match only client pods (e.g.,role: client). - Specified the required
portsfor ingress. - Introduced an
egressrule that permits traffic to any namespace (namespaceSelector: {}) on the ports the gateway needs. - Retained
policyTypes: [Ingress, Egress]to keep egress control explicit.
Apply the corrected policy:
kubectl apply -f corrected-gateway-np.yaml
Verify
After applying the new policy, perform the following checks:
- Confirm NetworkPolicy is active
kubectl get networkpolicy allow-gateway-ingress -n prod -o wide
curl -s -o /dev/null -w "%{http_code}" http://gateway-prod.prod.svc.cluster.local/healthz
# Expected output: 200
kubectl exec -n prod -c gateway-prod $(kubectl get pod -n prod -l app=gateway-prod -o jsonpath="{.items[0].metadata.name}") -- curl -s http://auth-service.prod.svc.cluster.local/ping
# Expected output: pong
kubectl exec -n kube-system -l k8s-app=calico-node -c calico-node -- iptables -S | grep DROP | grep gateway-prod
# Should return no lines related to the gateway
Verify that request latency returns to baseline (<30 ms) and that the gateway’s request count rises in Prometheus.
Prevent
Adopt the following guardrails to avoid similar regressions:
- Policy Review Checklist
Item Verification Ingress fromselectors are specificConfirm podSelectoris not empty unless intentionalEgress rules defined when policyTypesincludes EgressEnsure at least one toclause or explicitly allow allPorts listed match service requirements Cross‑check with Service and Deployment specs - Automated CI linting – Use
kube‑valorconftestpolicies to reject NetworkPolicies with empty selectors. - Canary rollout – Apply new NetworkPolicy to a staging namespace first; run smoke tests before promoting to production.
- Observability – Enable Calico’s
policyLoglevel and create alerts on “policy denied” log spikes.
Related Topic Hub: Distributed Systems Troubleshooting Hub
FAQ
- Why did the gateway lose inbound traffic even though the policy only targeted the
frontendnamespace?
The emptypodSelectorunderfrommatched *all* pods in thefrontendnamespace, including the LoadBalancer IP that terminates outside the cluster. Since the policy applied to the gateway pods, any traffic not matching the selector (e.g., external IP) was dropped. - Can I use
namespaceSelector: {}to allow all namespaces for egress?
Yes. An emptynamespaceSelectormatches every namespace. Pair it with apodSelectoror specificportsto avoid a blanket “allow all” that defeats security. - How do I debug which NetworkPolicy is denying traffic when multiple policies exist?
Enable the CNI’s policy logging (e.g.,calicoctl node run --policy-log-level=debug) and grep for “denied”. The log includes the policy name, source pod, destination pod, and port, allowing you to pinpoint the offending rule. - Is it safe to omit the
egresssection if I only need to control ingress?
IfpolicyTypesincludesEgress, an emptyegressarray defaults to “deny all egress”. Either removeEgressfrompolicyTypesor explicitly allow required egress destinations. - What monitoring alerts should I set for NetworkPolicy failures?
Create a Prometheus alert on thecalico_policy_denied_totalmetric (or equivalent for your CNI) with a threshold of >0 over a 5‑minute window. Pair it with a log‑based alert on “policy denied” messages.