Cloud NAT Gateway Port Exhaustion: Quick Fixes
NAT gateway port exhaustion kills outbound connections. Here's how to spot it and three fixes that actually work, starting with the most common cause.
Too Few NAT IPs (The Obvious Culprit)
This is the one I see 80% of the time. You set up a Cloud NAT—on GCP, AWS, or Azure—with a single IP address. That gives you about 64,000 ephemeral ports per IP. Sounds like a lot until you have a few hundred VMs all hitting the same external service. They burn through those ports in minutes.
You'll see Connection Refused errors, timeouts, or apps hanging. The fix is straightforward: add more NAT IPs. Each additional IP gives you another 64,000 ports. The math is simple.
# GCP example — add 3 more IPs to your Cloud NAT
gcloud compute routers nats update nat-gateway \
--router=my-router \
--region=us-central1 \
--nat-external-ip-pool=ip-address-1,ip-address-2,ip-address-3
For AWS, you switch to a NAT Gateway with multiple Elastic IPs (though AWS NAT GW gives you only 55,000 per gateway, so you might need multiple gateways). For Azure, scale out your NAT gateway to 16 IP addresses max.
Real-world trigger: A Kubernetes cluster with 50 nodes making outbound calls to an external API every second. The NAT IP runs out of ports in under 2 minutes. Adding 4 IPs fixes it cold.
Don't bother tuning TCP keepalives or adding connection pools until you've thrown more IPs at the problem. They're rarely the root cause.
Short Connection Timeouts and No Connection Reuse
Even with enough IPs, you can still hit port exhaustion if your apps don't reuse connections. Every new TCP connection grabs a port. If you close and reopen connections rapidly—like a web scraper or a Lambda calling an API—you'll fill up ports faster than they can drain.
The default TCP idle timeout on most cloud NATs is 5 minutes. That means a port stays locked for 5 minutes after the connection closes. If you're making 10,000 new connections per minute, you need 50,000 ports just for the ones in cooldown.
Fix #1: Lower the idle timeout. On GCP, you can set it down to 10 seconds:
gcloud compute routers nats update nat-gateway \
--router=my-router \
--region=us-central1 \
--min-ports-per-vm=64 \
--tcp-established-idle-timeout=10s \
--udp-idle-timeout=10s
Fix #2: Enable connection reuse on the app side. HTTP keepalives, connection pooling (like HikariCP for Java, or aiohttp for Python), and gRPC streaming keep connections alive and reuse them. This cuts port consumption by 10x or more.
I've seen a team who had a microservice calling an external REST API with a fresh connection per request. They were hitting port exhaustion with 5 NAT IPs. Adding connection pooling dropped the port count from 30,000 to 2,000. Night and day.
Don't bother increasing VM-level ephemeral port ranges—that's a red herring. The bottleneck is the NAT, not the VM.
Asymmetric Routing or DNS Misconfiguration
This is rarer but nasty when it hits. You add more IPs, you lower timeouts, you add pooling—still see port exhaustion in the logs. The problem is that traffic isn't routing through the NAT correctly. Maybe your VMs have public IPs directly assigned, or you've got a VPN that bypasses the NAT for some traffic.
When traffic leaves via the NAT but returns via a different path, the NAT never sees the connection close. Those ports stay open until the NAT decides they're idle (up to 5 minutes by default). Over time, that leaks ports.
Check your routing tables. On GCP, make sure you don't have a default route that points elsewhere:
gcloud compute routes list --filter="network=default"
Look for a route with next-hop-gateway set to the internet gateway and also a route with next-hop-vpn-tunnel. If both exist, you've got split traffic.
Fix: Remove any public IPs from VMs behind the NAT. Force all egress through the NAT gateway. On AWS, that means no public IPs on the instance ENI. On GCP, no external IP on the VM. On Azure, use a NAT gateway in front of the subnet and don't assign public IPs.
Also check DNS. If your VMs resolve external domains to a public IP that belongs to your cloud (like a GCP load balancer), the traffic might hit that directly instead of going through the NAT. Use private DNS zones to keep traffic on the internal network.
I hit this once with a customer who had a VPN to on-prem and a NAT gateway. Half their traffic went through the VPN, half through the NAT. Ports leaked like a sieve. Took us two hours to trace it. Don't let that be you.
Quick-Reference Summary Table
| Cause | Fix | Priority |
|---|---|---|
| Too few NAT IPs | Add more IP addresses to the NAT gateway | #1 — always check first |
| Short-lived connections, no reuse | Lower idle timeout, enable connection pooling | #2 — common after adding IPs |
| Asymmetric routing or DNS | Force all egress through NAT, remove public IPs, check DNS resolution | #3 — less common but critical |
Was this solution helpful?