Skip to content

All services are healthy. Who used the last outbound port?

The Network Was Up. The Requests Were Not. · Part 4 of 6 · 6 min read

Read the worked explanation ↓

Interview question. A fleet of workers in private subnets calls the same third-party payments API through an AWS NAT gateway. At peak, new HTTPS connections intermittently fail, but calls over established connections often succeed. CPU on the workers is low, gateway data throughput is nowhere near its advertised limit, and scaling the worker fleet makes matters worse. What finite resource would you look for?

I would inspect NAT source-port allocation before treating this as an application crash. Network address translation lets many private clients share a smaller set of outward-facing IP addresses. To keep replies tied to the right connection, the gateway maintains translations involving source addresses and ports plus the destination tuple and protocol. Those translated port allocations are finite.

AWS documents that one IPv4 address on a NAT gateway supports up to 55,000 simultaneous connections to each unique destination, where unique means destination IP, port and protocol. That's a service-specific limit, not a universal constant for every NAT appliance. If thousands of workers open short-lived TLS connections to the same 203.0.113.20:443 destination, they compete for that particular allocation budget.

This makes the failure look strange. A modest stream of requests to many destinations may work perfectly, while a concentrated burst to one destination exhausts ports even though the gateway can carry much more aggregate bandwidth.

Many private workers share the finite translated source-port pool for one public destination, even though the NAT gateway has spare throughput.
Many private workers share the finite translated source-port pool for one public destination, even though the NAT gateway has spare throughput.
Think first

Three hundred workers each call the same external API at 10 requests per second. Every call opens its own TLS connection and translations remain allocated for an assumed 25 seconds on average. Estimate the connection population for one destination, evaluate the 55,000-per-address AWS limit and propose both immediate and architectural fixes.

Take a few minutes to form an approach, then compare it with the worked explanation.

Check the exercise numbers

With one new connection per call, 300 × 10 = 3,000 new connections per second. At the assumed 25-second average allocation lifetime, offered steady-state demand is 3,000 × 25 = 75,000 mappings to that destination—20,000 above one address’s 55,000 limit. Two adequately distributed address budgets are the arithmetic minimum, not a guarantee of sufficient headroom or successful admission. Occupancy and destination concentration must be measured.

Reveal the worked explanation

Work out the connection population

Suppose the fleet establishes 1,200 outbound connections every second to that destination, and each translation remains occupied for an average of 60 seconds under the observed traffic and gateway behavior. A simple steady-state estimate gives 1,200 × 60 = 72,000 simultaneously occupied mappings. That exceeds the 55,000-per-destination capacity of one NAT IPv4 address. The assumed 60-second occupancy must be measured, not treated as an AWS default.

This is a version of Little's Law applied to active translated connections. The important quantities are new connections per second and how long translations consume scarce state. API requests per second is not the same as new TCP connections per second. One persistent connection can carry many sequential requests and, with HTTP/2 where supported, multiple concurrent streams. Conversely, 200 workers opening fresh connections for each small request can burn thousands of translations for very little payload.

This is why blindly increasing application replicas may amplify the incident. More replicas can create more separate pools and more simultaneous dial attempts. If they all exit through the same NAT public IPv4 address toward one upstream destination, the scarce pool is still shared. Horizontal scaling adds new consumers of a limit instead of increasing that limit.

Prove where ports disappear

For an AWS NAT gateway I would chart ErrorPortAllocation and PacketsDropCount, correlate them with new outbound dial errors, and inspect destination concentration using flow logs and application metrics. AWS also exposes other NAT metrics that help separate idle timeouts and connection attempts. I'd identify the precise destination IPs and ports, remembering that a provider hostname can resolve to multiple addresses or change over time.

I would separately rule out source-host ephemeral-port exhaustion, socket-descriptor limits and a local connection-pool cap. They produce similar client symptoms but require different fixes. When local ephemeral ports run out, the operating system may fail connect() before traffic reaches the NAT gateway. When the gateway runs out of translated ports, the host may initiate connections that cannot receive a working mapping. Client exception wording alone is not a reliable location detector.

Another clue is the difference between reused and new connections. If established connections carry requests successfully but fresh connection attempts fail at the same time gateway port-allocation errors rise, that is strong evidence for NAT exhaustion. Still check the upstream's own connection admission and firewalls, because overload can coexist at multiple stages.

Fix it in the right order

The first application change is usually to stop treating every API call as a new TCP handshake. Reuse connections through bounded, correctly configured clients. Cap pool expansion and the number of concurrent dials. Spread reconnects with jitter so a rolling restart or upstream idle timeout doesn't trigger synchronized storms. Account for safe eviction of stale connections, because keeping connections forever can also be wrong.

If service behavior allows it, HTTP/2 multiplexing reduces the number of TCP connections needed for simultaneous requests. But a provider may not support it, and concentrating everything on one connection introduces its own flow-control or failure coupling. Tune by observed concurrency, response latency and connection health rather than making protocol assumptions.

At the network layer, AWS documents adding secondary IPv4 addresses to expand NAT connection capacity. Another design might distribute traffic through multiple NAT gateways and appropriate routes, or use private endpoints for supported services so it does not traverse public NAT. These changes have cost, route, security and failure-domain implications. They should be justified with destination-specific measurements, not a generic instruction to add gateways.

I would be cautious about simply lowering timeouts. Shorter client timeouts can increase connection churn, which consumes even more NAT state, and can leave payment operations ambiguous. A bounded request budget and upstream rate limit reduce demand more predictably. If a provider publishes capacity constraints, align outbound admission with them rather than retrying aggressively.

The steady-state target is not zero connection errors at arbitrary offered load. It is a predictable maximum number of live translations, a controlled dial rate during failure or restart, and clear backpressure to callers when capacity is unavailable. Measure successful completions, fresh dials per second, occupancy proxies, NAT allocation failures and upstream p99 together.

The useful distinction is between bandwidth capacity and connection-identity capacity. Both live in the network, but changing one does not necessarily improve the other. The next incident will have neither high bandwidth use nor a port shortage. Small packets get through, and large ones disappear.

Go deeper

A NAT mapping is not simply "one port per application request". Protocol, source tuple, destination tuple, gateway implementation, state lifetimes and reuse policies determine how translations are allocated. The cited AWS quota is per unique destination per NAT IPv4 address. Multiple remote IPs can each have their own allocation budget. Connection-pool reuse reduces new mappings, but keepalive and upstream idle-timeout behavior must match. AWS flow logs and NAT CloudWatch counters can support a diagnosis, but do not equate NAT PacketsDropCount with ErrorPortAllocation without inspecting their definitions.

Check your reasoning

  • Describe why NAT translations require a finite source-port mapping.
  • Apply the AWS per-destination per-IPv4 simultaneous connection figure precisely.
  • Estimate connection mapping occupancy from establishment rate and holding time.
  • Separate gateway ErrorPortAllocation from source-host port or descriptor exhaustion.
  • Design bounded reuse, admission, network capacity and protected retries.

Common mistake: An AWS NAT throughput quota is sufficient to determine its concurrent-connection capacity to one upstream IP.

Which response addresses destination-specific NAT port exhaustion?

Read the answer explanation

Reuse bounded connections and measure ErrorPortAllocation, then add NAT IP capacity if needed — Port allocation is a finite destination-specific resource, not the same as bytes per second.

Apply it elsewhere

A Kubernetes platform shifts service-to-service calls from public NAT to a private endpoint. Explain which limits move, which identity and access rules change, and how you would validate that the new route does not create a shared bottleneck.

Compare your approach

Define NAT tuple mapping, apply the AWS per-destination limit correctly, calculate connections using rate times holding time, distinguish source host from gateway exhaustion, and discuss connection reuse and network-topology tradeoffs.

One more constraint

If a NAT gateway has a 10 Gbps throughput capacity, does that prove it can support any number of simultaneous connections to one destination?

Quick revision

Many private clients can exhaust a NAT gateway's finite translated source-port capacity even with low CPU and plenty of bandwidth. AWS NAT gateways document up to 55,000 simultaneous connections per IPv4 address to one unique destination IP, port and protocol. Estimate mappings as new connections per second multiplied by occupancy duration. Check ErrorPortAllocation, flows and fresh-dial failures, then reuse and bound connections, reduce reconnect storms or add appropriately routed NAT address capacity. Adding workers alone can worsen the problem.

References