Azure Service Bus Autoscale Cooldowns: How It Actually Works

The official documentation for Azure Monitor Autoscale lacked a critical detail regarding how cooldown periods are evaluated.

I discovered this gap during a real-world incident. My environment scaled down as expected when CPU usage dropped. However, 15 minutes later, an unexpected CPU spike hit the system. To my surprise, the environment did not scale out. The scale-out action was blocked, waiting for its own long cooldown period to expire, even though the last action was a scale-in.

I wanted to understand exactly how the engine was making these decisions, so after running specific tests and analyzing the AutoscaleEvaluationsLog, I discovered the underlying truth: cooldowns in Service Bus Autoscale are evaluated per rule, not per profile, and are counted from the last scale action, no matter the direction.

Because this wasn’t clearly stated, I submitted a Pull Request to the official Microsoft documentation to address the gap. You can view the PR here: https://github.com/MicrosoftDocs/azure-monitor-docs/pull/303.

The Microsoft team merged it, noting: “You caught a real gap in our docs.”

Here is a breakdown of what I found during those tests and how the Autoscale engine really behaves.

Looking Under the Hood

When configuring an Autoscale setting in the Azure Portal, you create a Profile and add rules (e.g., “Scale Out” when CPU > 80%, and “Scale In” when CPU < 20%). I wanted to know how exactly the cooldowns interact between opposing rules at the engine level.

My tests provided a clear answer. In one scenario, a Scale-Up rule had a 25-minute cooldown, but a Scale-Down action triggered just 6 minutes later.

What Actually Happens: The LastScaleActionTime

By analyzing the AutoscaleEvaluationsLog via Log Analytics, here is exactly what the engine was doing.

The crucial detail to understand is a property in the logs called LastScaleActionTime. The Autoscale engine records a single, unified timestamp whenever any scaling action (in or out) successfully executes within the profile.

When evaluating the rules, the engine checks the time elapsed since this shared LastScaleActionTime. However, it evaluates that time elapsed against the individual cooldown period configured on the specific rule it is currently checking.

Here is how that played out in my tests:

  1. The Trigger: A Scale-Up action successfully fired. The engine updated the LastScaleActionTime.
  2. The Next Cycle: A few minutes later, the engine evaluated the metrics again.
  3. Evaluating Scale-Up: It checked the Scale-Up rule, looked at the LastScaleActionTime, saw the 25-minute cooldown hadn’t expired, and blocked it.
  4. Evaluating Scale-Down: Crucially, the engine didn’t stop. It moved to evaluate the Scale-Down rule. This rule had its own independent, shorter cooldown. The engine checked the same LastScaleActionTime, but because that specific rule’s timer had elapsed, the evaluation proceeded.
  5. The Action: The metric condition was met, and the scale-down triggered.
  6. The LastScaleActionTime was updated for all rules.

The Proof (KQL)

If you are shipping your Autoscale logs to Log Analytics, you can verify this behavior yourself. Look for the transition from a blocked rule to a triggered one within the same CorrelationId:

AutoscaleEvaluationsLog
|where OperationName in ("ScaleRuleCooldownEvaluation", "ScaleRuleEvaluation", "InstanceUpdateEvaluation")
|projectTimeGenerated,OperationName,SkipRuleEvaluationForCooldown,LastScaleActionTime,InstanceUpdateReason, ShouldUpdateInstance, CorrelationId
|order by TimeGenerated asc

When you run this query, pay close attention to the OperationName to see exactly where the time counter stands:

  • ScaleRuleCooldownEvaluation: This tells you the time counter is still inside the cooldown period for that specific rule. The rule is blocked from proceeding.
  • ScaleRuleEvaluation: This event means the time counter is outside the cooldown period. The engine is now actively checking the rule’s metric thresholds to see if it should fire.

Every rule looks at the same LastScaleActionTime, but each rule uses its own cooldown timer to decide if it is allowed to fire. That’s how the Autoscale engine manages capacity for the Service Bus Premium tier where these dynamics are critical.

The Real-World Consequences

Understanding this mechanism is vital because failing to account for it can lead to severe operational issues.

Consider this scenario: You set your scale-out rule to have a long cooldown (e.g., 1 hour) to prevent flapping, but leave your scale-in rule with a short cooldown. A scale-in action occurs as traffic drops. Just 15 minutes later, a massive spike in messages hits your queue.

Because the scale-out rule evaluates against the LastScaleActionTime (which was the scale-in 15 minutes ago), your system is blocked from scaling up for the remaining 45 minutes. You end up with 45 minutes of heavy message spikes hitting a severely under-provisioned Service Bus (fewer Messaging Units than you started with). It gets ugly fast.

Depending on your rule sets, this isolated timer mechanism can introduce unexpected capacity fluctuations and significantly alter how your Service Bus behaves.

A Curious Discrepancy: Does Behavior Vary By Resource?

Moreover, I have observed hints that this autoscale cooldown assessment might actually differ depending on the underlying Azure resource.

While my investigation focused heavily on the Service Bus, the Microsoft reviewer who validated my PR tested the behavior using an App Service Plan. Interestingly, the log events they posted in the updated documentation differ from mine. Their cooldown evaluation log contained an array of scale directions, a property that was completely absent from my Service Bus logs.

This strongly implies that the Autoscale behavior, or at least its log schema, diverges between App Service Plans and Service Bus. I plan to investigate this inconsistency next, along with other resources like Azure Stream Analytics.

But until then, the KQL query provided above remains your best tool to debug and verify the exact scaling behavior for any resource in your architecture.

Custom Domain Resolution over a Azure Private Endpoint

When you pair an Azure App Service with a Private Endpoint, your goal is simple: internal clients should resolve yourcompany.com to the Private Endpoint’s IPv4 address, never the public one. In my recent deployment, a temporary A record in the public zone was created only for domain‑ownership validation and deleted immediately after. The authoritative record lives exclusively in your internal DNS zone, giving you actual split‑horizon resolution. The sections below illustrate how that flow works and where it can go astray.

Author’s note
This article originates from two pull requests I recently submitted to Microsoft’s Azure documentation to clarify Private Endpoint configuration and its limitations PR #126574 and a follow-up PR #126580. The material captures nuances that, in practice, remain less than obvious.


1. Definitions

Term Meaning
Public 
DNS
The outward-facing DNS infrastructure reachable by the entire Internet. Records reside with your registrar or public DNS provider.
Private 
DNS
A DNS zone whose visibility is limited to your internal network (for example, Azure Private DNS).
It enables split-horizon resolution.
A record Maps a hostname directly to an IPv4 address. Simple, but brittle when the target IP is dynamic.
TXT record Stores free-form text used for domain validation (e.g., Azure, Google), SPF, DKIM, and other metadata. It does not affect name resolution.
Domain
Validation
A one-time procedure in which you add a temporary A or TXT record to prove ownership of the domain to Azure (or another provider). After verification, the record can be safely removed from public DNS.
Split-horizon DNS A setup in which the same domain name resolves to different records depending on whether the query originates inside or outside the network, allowing you to expose public records externally while serving private records internally.
Split-horizon record A DNS record that exists only in the internal zone (or differs internally) so that internal clients resolve a private endpoint while external clients receive no record or a different public record.

2. Domain Name Resolution Journey

Step Action
1. Client query A device inside your VNet (or a peered network) asks:
“Where is www.yourcompany.com ?”
2. Split-horizont record Public DNS now returns no record (NXDOMAIN) because the verification A record was removed. Your internal DNS zone hosts the record instead, either a CNAME pointing to
<app-name>.azurewebsites.net or an A record for the Private Endpoint’s IP.
3. Public to Private alias Azure’s internal resolver automatically rewrites
<app-name>.azurewebsites.net to
<app-name>.privatelink.azurewebsites.net
.
4. Private DNS lookup Because your VNet is linked to the privatelink.azurewebsites.net Private DNS zone, the resolver answers with the Private Endpoint’s IP.
5. TLS connection The client opens an encrypted session directly to the App Service over the private IP.

3. A vs CNAME

Why is an internal A record fragile?

  • Private Endpoint IP addresses can change during scaling, platform maintenance, or redeployment.
  • Updating an A record is manual; stale DNS can cause HTTP 404 TLS handshake failures.
  • Certificates are still validated by hostname, but the connection cannot be made once the IP drifts.

Preferred approach: internal CNAME

  1. In your internal DNS zone, create a CNAME:
    www.yourcompany.com to <app-name>.azurewebsites.net
  2. Ensure the VNet is linked to the privatelink.azurewebsites.net zone so that the alias resolves privately.
  3. Future IP changes are absorbed by Azure; no operator action is required.

4. Common Misconfigurations

Misstep Result Correction
Publishing an A record to the Private Endpoint IP IP changes silently; clients break. Use an internal CNAME; let Azure track the IP lifecycle automatically.
Forgetting to link the Private DNS zone to all VNets Some subnets fall back to public DNS, defeating isolation. Establish VNet links, or use DNS forwarding.
Allowing external resolvers to answer internal names Traffic exits and re-enters the network, complicating firewall rules. Ensure split-horizon DNS: internal queries are resolved internally.

5. Unique Default Hostnames

Since November 2024, Azure App Service allows you to opt in to secure unique default hostnames. A newly created web app receives a randomly‑hashed, region‑scoped address such as:

<app>-a6gqaeashthkhkeu.eastus-01.azurewebsites.net

Why did Microsoft introduce the change?

Microsoft explicitly states that the feature mitigates sub‑domain takeover caused by dangling DNS records and prevents accidental name collisions. Key references:

Impact on the Private Endpoint pattern

The DNS chain itself is unchanged; only the middle CNAME grows longer:

www.yourcompany.com  ->  <app>-hash.region.azurewebsites.net
                                              <app>-hash.region.privatelink.azurewebsites.net  -> 10.x.x.x
  • No additional Private DNS zones are required- continue linking privatelink.azurewebsites.net to your VNets; Azure auto‑populates the A record that maps to the Private Endpoint.
  • Split‑horizon guidance remains the same – keep the public zone empty, and create or retain an internal CNAME so any future IP rotation is invisible to consumers.
  • Automation & monitoring – update scripts or probes that are hard‑coded with the shorter <app>.azurewebsites.net pattern.

Enable the feature for green‑field apps whenever possible; it strengthens the DNS chain your Private Endpoint relies on with zero extra runtime configuration.


6. Frequently Annoying Questions

Does the Private Endpoint operate as a load balancer?

Not directly. Think of it as a sealed door: traffic passes through Azure’s managed App Service fabric, where Microsoft’s load balancers handle distribution. You gain private access and built‑in redundancy without maintaining a separate LB resource.

Why is the custom domain resolved even when I never added it to my private DNS?

Considering the CNAME approach, CNAME ends at privatelink.azurewebsites.net, and that zone is in your Private DNS setup. Therefore, the domain’s final resolution inherits the private mapping automatically – no duplicate records are needed.
While going with an A record… This means the custom domain was added to your private DNS at some point ;).


Closing Thought

DNS underpins connectivity and can be unforgiving. Treat the Private Endpoint as your application’s insurance number: keep it private, and verify twice before moving on. That extra diligence prevents the 2 a.m. incident call, arguably the most persuasive metric.