<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dharshini Sridharan</title>
    <description>The latest articles on DEV Community by Dharshini Sridharan (@dharshsrii).</description>
    <link>https://dev.to/dharshsrii</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F99mvlsfu5tfj9m7ku25d.png</url>
      <title>DEV Community: Dharshini Sridharan</title>
      <link>https://dev.to/dharshsrii</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dharshsrii"/>
    <language>en</language>
    <item>
      <title>Intermittent Timeouts Every Evening, Zero Deployments: The Azure SNAT Port Exhaustion Story</title>
      <dc:creator>Dharshini Sridharan</dc:creator>
      <pubDate>Mon, 26 Oct 2026 11:01:26 +0000</pubDate>
      <link>https://dev.to/dharshsrii/intermittent-timeouts-every-evening-zero-deployments-the-azure-snat-port-exhaustion-story-17ci</link>
      <guid>https://dev.to/dharshsrii/intermittent-timeouts-every-evening-zero-deployments-the-azure-snat-port-exhaustion-story-17ci</guid>
      <description>&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An order-processing API on Azure App Service worked fine all day. Every evening between 7 and 9 PM, during peak traffic, users got random 500s and timeouts. CPU was at 40%, memory was fine, and the database showed no slow queries. A restart “fixed” it for an hour, which made it feel like a ghost.&lt;/p&gt;

&lt;p&gt;The app calls a third-party payment API and Azure services (Azure SQL, Storage). The errors in Application Insights looked like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;System.Net.Http.HttpRequestException: Cannot assign requested address&lt;br&gt;
System.Net.Sockets.SocketException: An attempt was made to access a socket in a way forbidden...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those messages are the clue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Is SNAT Port Exhaustion?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6sbn96q0iur1ou2hw4e1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6sbn96q0iur1ou2hw4e1.png" alt=" " width="799" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When your app makes an outbound call to a public IP address, Azure translates your private address to a shared public one using SNAT (Source Network Address Translation). Each outbound connection to the same destination IP and port needs one SNAT port, and the supply is limited per instance. App Service preallocates only a small number per instance (128 by default, per Azure docs; check the current limits).&lt;/p&gt;

&lt;p&gt;If your code opens new connections faster than old ones are released (they linger until the idle timeout, about 4 minutes by default), you run out of ports. New outbound calls then fail or hang until ports free up. That’s why it only appeared at peak load and why a restart helped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Confirm It’s SNAT&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fepkyat76hqoj6hvxjqig.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fepkyat76hqoj6hvxjqig.png" alt=" " width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the App Service, open Diagnose and solve problems and search for SNAT Port Exhaustion. The detector shows failed connection attempts over time.&lt;/li&gt;
&lt;li&gt;Query Application Insights to see the failure pattern:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kusto
dependencies
| where timestamp &amp;gt; ago(24h) and success == false
| summarize failures = count() by target, resultCode, bin(timestamp, 15m)
| order by timestamp desc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Failures clustered on one or two targets (the payment API, for example) during peak hours point to SNAT.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Find the Root Cause in Code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The culprit was a classic anti-pattern in the payment client:&lt;/p&gt;

&lt;p&gt;csharp&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BAD: creates a new connection every call&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ChargeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.payments.example.com/charge"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsStringAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Disposing HttpClient doesn’t immediately free the socket, so under load each request burns a new SNAT port. With hundreds of requests per minute across a few instances, the port pool was gone in seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: The Fix (Three Layers)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Layer 1: Reuse connections in code (the biggest win)&lt;/p&gt;

&lt;p&gt;csharp&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Program.cs&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"payments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.payments.example.com/"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigurePrimaryHttpMessageHandler&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SocketsHttpHandler&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PooledConnectionLifetime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;csharp&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Usage: inject IHttpClientFactory&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"payments"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connections are now pooled and reused, so far fewer SNAT ports are needed. The same principle applies to Python (requests.Session) and Node (keep-alive agents).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 2: Add a NAT Gateway for much more capacity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With regional VNet integration, route outbound traffic through a NAT Gateway. Each public IP on a NAT Gateway provides about 64,000 SNAT ports, shared dynamically, instead of a small fixed slice per instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash
&lt;span class="c"&gt;# Public IP + NAT Gateway&lt;/span&gt;
az network public-ip create &lt;span class="nt"&gt;-g&lt;/span&gt; rg-orders &lt;span class="nt"&gt;-n&lt;/span&gt; pip-nat &lt;span class="nt"&gt;--sku&lt;/span&gt; Standard &lt;span class="nt"&gt;--allocation-method&lt;/span&gt; Static
az network nat gateway create &lt;span class="nt"&gt;-g&lt;/span&gt; rg-orders &lt;span class="nt"&gt;-n&lt;/span&gt; natgw-orders &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--public-ip-addresses&lt;/span&gt; pip-nat &lt;span class="nt"&gt;--idle-timeout&lt;/span&gt; 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Attach to the App Service integration subnet&lt;/span&gt;
az network vnet subnet update &lt;span class="nt"&gt;-g&lt;/span&gt; rg-orders &lt;span class="nt"&gt;--vnet-name&lt;/span&gt; vnet-orders &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; snet-appsvc-integration &lt;span class="nt"&gt;--nat-gateway&lt;/span&gt; natgw-orders

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Route all outbound traffic through the VNet&lt;/span&gt;
az webapp config &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; rg-orders &lt;span class="nt"&gt;-n&lt;/span&gt; orders-api &lt;span class="nt"&gt;--generic-configurations&lt;/span&gt; &lt;span class="s1"&gt;'{"vnetRouteAllEnabled": true}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bonus: you now have a single, static outbound IP, which the payment provider can whitelist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 3: Keep Azure-to-Azure traffic off the internet path&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use Private Endpoints for Azure SQL, Storage, and Key Vault so that traffic uses private IPs and consumes no SNAT ports at all.&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az network private-endpoint create &lt;span class="nt"&gt;-g&lt;/span&gt; rg-orders &lt;span class="nt"&gt;-n&lt;/span&gt; pe-sql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vnet-name&lt;/span&gt; vnet-orders &lt;span class="nt"&gt;--subnet&lt;/span&gt; snet-private-endpoints &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--private-connection-resource-id&lt;/span&gt; &amp;lt;sql-server-resource-id&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--group-id&lt;/span&gt; sqlServer &lt;span class="nt"&gt;--connection-name&lt;/span&gt; sql-conn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember to link the matching Private DNS zone (privatelink.database.windows.net) to the VNet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Prevent a Repeat&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;- Create an Azure Monitor alert on the NAT Gateway metric SNAT Connection Count (filter on failed state), plus App Service connection metrics.&lt;/li&gt;
&lt;li&gt;- Add a dependency-failure alert in Application Insights for any target exceeding a threshold.&lt;/li&gt;
&lt;li&gt;- Add a code review checklist item: “No new HttpClient() inside request paths.”&lt;/li&gt;
&lt;li&gt;- Include NAT Gateway and private endpoints in your Bicep/Terraform baseline modules.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Results&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this kind of scenario, connection reuse alone typically removes most of the failures, and the NAT Gateway adds headroom for growth. Evening 500s drop to near zero, and no more restarts are needed as a workaround.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Works after restart, fails at peak” is a signature of connection or port exhaustion.&lt;/li&gt;
&lt;li&gt;SNAT limits are per instance and easy to hit when clients aren’t reused.&lt;/li&gt;
&lt;li&gt;Fix it in layers: connection pooling in code, NAT Gateway for capacity, private endpoints to avoid SNAT entirely.&lt;/li&gt;
&lt;li&gt;Monitor SNAT metrics before users find the problem for you.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>blog</category>
    </item>
  </channel>
</rss>
