Every Cloud Lets Traffic Out a Different Way: Designing One Egress Policy for a Multi-Cloud Estate
Most teams can describe how traffic gets into their estate. Far fewer can describe how it gets out. Inbound paths get diagrams, reviews, and change tickets. Outbound paths tend to grow on their own. One cloud routes thr
Most teams can describe how traffic gets into their estate. Far fewer can describe how it gets out.
Inbound paths get diagrams, reviews, and change tickets. Outbound paths tend to grow on their own. One cloud routes through a NAT gateway in each availability zone. Another sends everything through a central inspection VPC. A third still lets workloads reach the internet directly from their subnets because that was the default when the account was created. Each choice made sense locally at the time. Together they mean there is no single answer to a simple question: what is a workload in this estate allowed to talk to outside, and where would anyone see it try?
Egress is where a lot of quiet risk lives. It is the path for software updates and package downloads, and it is also the path data leaves on if a workload is compromised, a credential is abused, or a misconfigured job starts syncing to the wrong bucket. Designing one egress policy across clouds does not mean forcing every provider through identical plumbing. It means agreeing on the same rules, the same evidence, and the same exceptions process, then implementing each one in the way that cloud actually works.
Start with an inventory, not a firewall rule
You cannot write a policy for destinations you have not listed. Start by collecting, for two normal weeks, every external destination your workloads actually reach, grouped by workload rather than by IP address.
Group destinations into a small set of buckets:
- Provider APIs and managed service endpoints that the workload needs to function
- Package registries, container registries, and operating system update sources
- Third party SaaS APIs the business has approved
- Telemetry, logging, and monitoring endpoints
- Everything else, which is the list that needs explaining
Expect surprises. Build agents phoning home to a vendor endpoint nobody approved. A library fetching a model or a lookup table at runtime instead of at build time. A health check or license check that runs every few minutes and would break the service if it were blocked without warning. Finding those before you enforce anything is the difference between an egress project and an outage.
Record the owner for each destination as you go. An unnamed destination will have no one to call when it stops working, and no one able to say whether it is still needed six months later.
Separate three kinds of outbound traffic
Treating all egress as one flow forces a bad choice between blocking too much and allowing everything. Three kinds behave differently and deserve different paths.
First, traffic to cloud provider services in the same ecosystem should stay private wherever a private endpoint exists. Object storage, secrets managers, queues, and managed databases rarely need to be reached over the public internet at all. Keeping that traffic private removes it from the egress debate entirely and removes a whole set of public exposure at the same time.
Second, traffic to approved external APIs usually needs name based control, not just IP based control. SaaS providers change addresses behind a stable hostname, use shared address ranges, or sit behind a CDN. A rule that allows an IP range today can silently allow a different service tomorrow, or break when the provider moves. An egress proxy or a firewall that can allow by fully qualified domain name, with TLS inspection only where policy and law actually require it, fits this traffic better than a long list of addresses.
Third, general internet access for builds and updates should be narrow, scheduled where possible, and separate from production runtime traffic. A build system pulling packages is not the same risk as a production service fetching arbitrary URLs at runtime. Where a runtime workload claims it needs open internet access, that is usually a design conversation waiting to happen, not a rule to approve quickly.
Make NAT a managed path, not an accident
Where traffic does leave through NAT, treat the NAT layer as part of the design. Decide deliberately whether egress is centralised per region, per cloud, or per workload tier, and write down what that choice costs and what it buys. Centralising simplifies logging and allowlisting and gives a small number of public source addresses that third parties can allowlist. It also creates a shared bottleneck and a shared failure point, and can add cross zone or cross region charges to traffic that never needed to travel that far.
Whichever pattern is chosen, apply it consistently enough that an investigator can follow it. Log which internal workload originated a flow, not just which NAT address it left from. Keep NAT port allocation and timeout behaviour in mind for high connection rate workloads, and alert on sustained utilisation rather than waiting for intermittent connection failures that look like application bugs. Give each environment, production, staging, and build, its own egress identity where practical, so a noisy or compromised non production system cannot spend the reputation, the quota, or the allowlist trust of production.
DNS is part of egress, whether it is treated that way or not
A workload that can resolve any public name and reach a resolver outside your control can often tunnel around an otherwise careful egress design. Route DNS through resolvers you operate or have chosen, log queries at the workload level where privacy rules allow it, and decide explicitly which zones and resolvers each cloud should use for private names.
Pay particular attention to fallback behaviour. Many outages and many quiet leaks share the same shape: the primary private resolver or private route fails, and the workload falls back to a public resolver or a public endpoint and keeps working, so nobody notices the private path stayed broken. A fallback that changes the security posture of a flow should fail visibly, or at minimum log at a level someone will actually read the next morning.
Write the policy once, translate it carefully
The policy itself should be short enough to be reviewed: which workload tiers may reach which destination buckets, through which kind of path, with which logging, and how an exception is requested and expired. The implementation will differ per cloud, in NAT placement, firewall syntax, proxy deployment, and log format. That difference is expected. What should not differ is the meaning.
Keep a translation table alongside the implementations. For each policy line, note how it is enforced in AWS, in Azure, in Google Cloud, and in Oracle, and note the gaps honestly where a cloud cannot enforce it in the same way. Those gaps are where audits stall and where incidents get confusing, because each side believes the rule is in place. Test the translation with negative tests: pick a destination that should be blocked for a given workload, try it from that workload in each cloud, and keep the evidence that it failed. A policy that has only ever been tested with traffic that should pass has not really been tested.
Exceptions need an expiry date by default. An allow rule added for a migration, a vendor test, or a one off data transfer has a habit of becoming permanent simply because removing it feels riskier than leaving it. Thirty or ninety days, with a named owner and a reason, is enough for most legitimate cases and forces a short conscious renewal instead of silent permanence.
A short checklist to start
- Inventory external destinations by workload for two weeks, group them into buckets, and name an owner for each.
- Move provider service traffic onto private endpoints wherever one exists, so it leaves the public egress path entirely.
- Split remaining traffic into approved API access and build or update access, and give each a path that matches its risk.
- Record NAT placement, source addresses, logging, and utilisation alerting as design decisions, not defaults.
- Route DNS through chosen resolvers, and make any fallback that changes security posture fail visibly.
- Write the policy once, keep a per cloud translation table with honest gaps, and prove blocked destinations are blocked with negative tests in each cloud.
- Give every exception an owner, a reason, and an expiry date.
Teams that want a structured way to apply one egress and zero trust policy consistently across AWS, Azure, Google Cloud, and Oracle estates can look at the approach set out at CloudVerg, where VergShield and VergMesh are used for exactly this kind of cross cloud security and connectivity work. The checklist above stands on its own either way. Start with the inventory. The first list of destinations nobody can explain is usually the most useful finding in the whole exercise.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.