Egress Traffic Control: A Default-Deny Cloud Guide

Egress traffic control is the discipline of deciding explicitly which outbound connections your cloud workloads may make, and then denying everything else. The default state on every major platform is the opposite: every outbound connection from a pod is allowed until a NetworkPolicy selects it for egress, and virtual networks in AWS and Azure similarly permit unrestricted internet access from the moment they are created. Moving to a default-deny egress posture is among the highest-leverage hardening steps a cloud team can take, because data exfiltration, command-and-control callbacks and cryptomining campaigns all depend on exactly one thing: a working route out.
Why cloud egress stays open
Permissive defaults exist for usability. A workload that can reach anything on the internet works on day one without a network ticket, and most teams never revisit that assumption after launch. The result is that a single compromised container, lambda function or virtual machine becomes a launchpad with unrestricted outbound reach.
The mechanics differ per platform. AWS attaches each new subnet to a shared ACL, and the default network ACL in every VPC allows all traffic in and out of the subnets it is associated with unless you replace it with a custom, stateless ACL. On Azure, every network security group is created with a default AllowInternetOutBound rule that permits all outbound traffic to the Internet unless a higher-priority rule overrides it. Kubernetes applies egress isolation only when a policy explicitly selects the pod, and multiple policies combine additively, so one missing pod selector quietly reopens the outbound path.
Standards bodies settled this question long ago. NIST recommends blocking all inbound and outbound traffic that the firewall policy does not expressly permit, a stance the firewall guideline SP 800-41 calls deny by default, because permitting everything that is not explicitly forbidden is the weaker posture on dynamic networks.
Comparing native egress controls
Each cloud gives you a different enforcement point, and most mature designs layer at least two of them: a coarse subnet-level guard plus a fine-grained workload policy.
| Control | Scope | Default egress behavior | What it can express |
|---|---|---|---|
| Kubernetes NetworkPolicy | Pod | Open until a policy selects the pod | Additive L3/L4 allow rules; no native deny rules or FQDN matching |
| AWS Network ACL | Subnet | Allow-all on the default ACL | Stateless numbered allow and deny rules by CIDR and port |
| AWS Security Group | Instance and ENI | Default group permits all outbound | Stateful allow rules by CIDR, prefix list or group reference |
| Azure NSG | NIC and subnet | Internet egress allowed by default | Allow and deny rules with service tags and application security groups |
NetworkPolicy is allow-only at the pod layer, which means broad denials have to come from an admin-level policy or a CNI such as Cilium that supports deny semantics. The Kubernetes network policy documentation is explicit that isolation for egress only takes effect when a selecting policy exists.
Rolling out default-deny egress
A blind flip to deny breaks production in hours. The sequence that survives contact with real workloads looks like this:
- Measure before you deny. Collect two to four weeks of flow logs from VPC Flow Logs, Azure Network Watcher or Cilium Hubble, and cluster the destinations your workloads actually touch.
- Build an allowlist from observed flows plus declared dependencies: container registries, package mirrors, identity providers, APIs, DNS resolvers and NTP. Flag anything unexplained rather than adding it silently.
- Pair the network allowlist with an IAM least-privilege review, because identity scope and network reach should shrink together or attackers will pivot through whichever is wider.
- Enforce in stages: start with a default-deny in one namespace or one subnet family, exempt DNS first, then tighten gradually. On Azure, add explicit deny rules with a priority number above the defaults; on AWS, prefer custom ACLs and outbound-restricted security groups. The AWS default network ACL reference documents exactly what the built-in rules permit before you replace them.
- Alert on denied flows and treat repeated denials as either a missing dependency or an investigation signal, never as noise to suppress.
- Codify every rule in infrastructure as code, so a manual console change shows up as drift in review rather than as a silent policy hole.
Trade-offs teams should plan
Default-deny egress is not free. SaaS providers rotate IP addresses, so CIDR-based allowlists go stale and generate outages that look like application bugs; service tags on Azure and FQDN-aware proxies or CNI policies on Kubernetes reduce that churn at the cost of another component to operate. Inspection tiers add latency and cost, and in multi-region topologies NAT gateways become a meaningful line item. Teams also meet the limits of layer 3 thinking: an allowlist pinned to a hostname is only as trustworthy as the resolver, which is why DNS paths deserve their own controls.
The upside compounds with adjacent programs. Workloads that can only reach an approved secrets endpoint reinforce a secrets management strategy, because ad-hoc fetching from random hosts becomes structurally impossible rather than merely discouraged. The Azure NSG fundamentals module walks through the outbound defaults that your override rules will replace. Start with observability, deny in narrow scopes, and expand the perimeter inward until the internet is no longer a default route for anything that does not need it.