Azure API Timeout: How to Fix 504, 408, and Operation Timed Out Errors
Fix Azure API timeouts (504, 408, TimeoutException) by adjusting APIM forward-request policy, Function host.json limits, and App Service timeouts. Step-by-step
- The most common cause is Azure API Management's default 60-second forward-request timeout, which silently drops long-running backend calls with a 504 Gateway Timeout
- Azure Functions HTTP triggers enforce a hard 230-second ceiling enforced by the Azure load balancer, regardless of host.json settings — long operations must be refactored to async/Durable Functions
- Cold starts in Consumption-plan Azure Functions can add 5–20 seconds of latency before your code even runs, causing downstream callers to time out first
- Quick fix for APIM: add <forward-request timeout="120" /> inside your inbound policy; quick fix for Functions: set functionTimeout in host.json and switch to Premium or Dedicated plan to eliminate cold starts
| Method | When to Use | Estimated Time to Apply | Risk |
|---|---|---|---|
| Increase APIM forward-request timeout | Backend legitimately needs >60 s; you control APIM policy | 5 min | Low — policy change, no redeploy |
| Bump Azure Function functionTimeout in host.json | Function runtime timeout hit before the 230 s load balancer ceiling | 10 min + redeploy | Low |
| Refactor to async HTTP + polling (202 Accepted) | Operation exceeds 230 s; cannot be shortened | Hours–days | Medium — API contract change |
| Migrate to Durable Functions | Complex long-running orchestrations or fan-out | Days | Medium — code rewrite |
| Move to Premium / Dedicated App Service Plan | Recurring cold-start timeouts on Consumption plan | 30 min | Low-Medium — cost increase |
| Add retry + exponential back-off in client | Transient network or throttling timeouts | 1–2 hours | Low — client-side only |
| Enable APIM caching or backend keep-alive | Repeated identical calls timing out due to connection overhead | 30 min | Low |
Understanding Azure API Timeout Errors
Azure surfaces timeout failures through several distinct error codes and messages depending on where in the request chain the deadline expires:
- 504 Gateway Timeout — Azure API Management (APIM) did not receive a response from the backend within the configured
forward-requesttimeout (default: 60 seconds). - 408 Request Timeout — The client's connection to APIM or the Azure load balancer itself expired before the server acknowledged the request.
- System.TimeoutException: The operation timed out — Thrown inside .NET code when an
HttpClientor SDK call exceeds itsTimeoutproperty. - Function host timed out after 00:05:00 — An Azure Function exceeded its
functionTimeoutvalue (default 5 min on Consumption, 30 min on Premium/Dedicated). - Upstream connect error or disconnect/reset before headers. reset reason: connection timeout — Seen in APIM developer portal traces when the backend TCP handshake itself fails.
Each of these has a different fix. The first step is always to identify where in the chain the clock ran out.
Step 1: Locate the Timeout Boundary
1a. Check APIM diagnostic logs in Azure Monitor.
Navigate to your APIM instance → Diagnostic settings → Log Analytics workspace. Run this KQL query:
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.APIMANAGEMENT"
| where Category == "GatewayLogs"
| where httpStatusCodeCategory_s == "5xx"
| project TimeGenerated, requestId_s, backendResponseCode_d, backendTime_d, totalTime_d, url_s
| order by TimeGenerated desc
If backendTime_d is close to 60000 ms and backendResponseCode_d is null, APIM timed out waiting for your backend — fix is in the APIM policy.
If backendTime_d is well under 60 s but the client still sees a 504, the issue is upstream (client → APIM leg) or a misconfigured custom domain / Front Door.
1b. Enable APIM tracing for a single request.
# Pass the Ocp-Apim-Trace header to get a trace URL back
curl -i -H "Ocp-Apim-Subscription-Key: <your-key>" \
-H "Ocp-Apim-Trace: true" \
"https://<apim-name>.azure-api.net/<api-path>"
# The response headers will contain:
# Ocp-Apim-Trace-Location: https://apimstxxxxxxx.blob.core.windows.net/...
# Fetch that URL for a full JSON trace showing every policy step and its duration
curl "<trace-location-url>" | python3 -m json.tool | grep -A3 'elapsed'
1c. Check Azure Function execution duration.
az monitor app-insights query \
--app <app-insights-name> \
--resource-group <rg> \
--analytics-query "
requests
| where timestamp > ago(1h)
| where success == false or duration > 25000
| project timestamp, name, duration, resultCode, cloud_RoleName
| order by duration desc
| take 50
"
Step 2: Fix APIM Policy Timeout
The forward-request policy element controls how long APIM waits for the backend. The default is 60 seconds.
In the Azure portal, go to your API → Design tab → select the operation → Inbound processing → edit the policy XML:
<policies>
<inbound>
<base />
<!-- Increase timeout to 120 seconds for this operation -->
<forward-request timeout="120" follow-redirects="true" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Warning: The Azure load balancer in front of APIM enforces a 240-second idle TCP timeout. Setting
forward-request timeoutabove 240 is ineffective — the connection will be silently reset by the load balancer first.
If you need timeouts per-operation rather than globally, scope the policy to the operation level rather than the API or product level.
Step 3: Fix Azure Functions Timeout
For Consumption plan functions, the default functionTimeout is 5 minutes; the maximum is 10 minutes. For Premium and Dedicated (App Service) plans the default is 30 minutes and there is no enforced maximum in the runtime — but the 230-second Azure load balancer limit still applies to HTTP triggers.
Edit host.json:
{
"version": "2.0",
"functionTimeout": "00:10:00",
"extensions": {
"http": {
"routePrefix": "api"
}
}
}
For operations that genuinely exceed 230 seconds over HTTP, implement the async HTTP polling pattern:
- Client POSTs to trigger function → function starts a Durable orchestration → immediately returns
202 Acceptedwith astatusQueryGetUri. - Client polls the status URL until
runtimeStatusis"Completed". - Client retrieves the output.
This pattern is fully supported by the DurableOrchestrationClient (now DurableClient) binding:
[FunctionName("StartLongJob")]
public static async Task<HttpResponseMessage> HttpStart(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestMessage req,
[DurableClient] IDurableOrchestrationClient starter,
ILogger log)
{
string instanceId = await starter.StartNewAsync("LongJobOrchestrator", null);
return starter.CreateCheckStatusResponse(req, instanceId);
}
Step 4: Fix Client-Side HttpClient Timeouts (.NET)
If the timeout originates inside your application code rather than the Azure infrastructure layer:
// WRONG — default Timeout is 100 seconds but is set once and cannot be changed after first use
var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(300); // must set BEFORE first request
// CORRECT — use IHttpClientFactory with Polly retry + timeout
services.AddHttpClient("AzureBackend", c =>
{
c.BaseAddress = new Uri("https://myapi.azure-api.net/");
c.Timeout = TimeSpan.FromSeconds(120);
})
.AddPolicyHandler(HttpPolicyExtensions
.HandleTransientHttpError()
.Or<TimeoutRejectedException>()
.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i))));
Step 5: Diagnose Cold-Start Latency
On Consumption plan, the first invocation after an idle period spins up a new host instance. Typical cold-start durations:
| Runtime | Typical Cold Start |
|---|---|
| .NET 8 isolated | 3–8 s |
| Node.js 20 | 2–5 s |
| Python 3.11 | 5–15 s |
| Java 17 | 8–20 s |
Mitigations (in order of cost):
- Premium plan with pre-warmed instances — eliminates cold starts entirely.
- Always-on setting (Dedicated plan) — keeps the host loaded.
- Azure Function's "Warm" HTTP trigger — a lightweight keep-alive pinger via Azure Monitor scheduled queries or Azure Logic Apps.
- Reduce package size — large deployment packages increase cold start time. Run
func pack --build-native-depsand check the output size.
Step 6: Verify Network Path (VNet Integration)
If your APIM backend is inside a VNet or the Function uses VNet Integration, check NSG rules and DNS:
# From APIM self-hosted gateway or a VM in the same subnet:
nc -zv <backend-private-ip> 443
# Check effective NSG rules on the backend NIC:
az network nic show-effective-nsg \
--resource-group <rg> \
--name <nic-name> \
--query "effectiveSecurityRules[?access=='Deny']" \
--output table
# Verify private DNS resolution inside the VNet:
nslookup <backend-hostname> 168.63.129.16
A missing or incorrect NSG outbound rule to the backend subnet on port 443 will cause the TCP SYN to be silently dropped, resulting in a connection timeout (usually at exactly 60 s — the APIM default) rather than a fast TCP RST.
Frequently Asked Questions
#!/usr/bin/env bash
# Azure API Timeout Diagnostic Script
# Prerequisites: az CLI logged in, curl, jq
APIM_NAME="your-apim-instance"
RG="your-resource-group"
API_PATH="/your-api/endpoint"
SUBSCRIPTION_KEY="your-subscription-key"
APIM_HOST="https://${APIM_NAME}.azure-api.net"
echo "=== 1. Check APIM instance state ==="
az apim show --name "$APIM_NAME" --resource-group "$RG" \
--query "{state:properties.provisioningState, sku:sku.name, tier:sku.capacity}" \
--output table
echo ""
echo "=== 2. Retrieve current forward-request timeout from effective policy ==="
az apim api policy show --api-id "your-api-id" \
--resource-group "$RG" --service-name "$APIM_NAME" \
--query value -o tsv 2>/dev/null | grep -o 'forward-request[^/]*' || \
echo "[INFO] No operation-level policy found; check API or product level"
echo ""
echo "=== 3. Test endpoint latency with timing breakdown ==="
curl -o /dev/null -s -w \
"dns_resolution: %{time_namelookup}s\ntcp_connect: %{time_connect}s\nssl_handshake: %{time_appconnect}s\ntime_to_first_byte: %{time_starttransfer}s\ntotal_time: %{time_total}s\nhttp_status: %{http_code}\n" \
-H "Ocp-Apim-Subscription-Key: ${SUBSCRIPTION_KEY}" \
--max-time 70 \
"${APIM_HOST}${API_PATH}"
echo ""
echo "=== 4. Enable APIM trace and fetch trace log ==="
TRACE_RESPONSE=$(curl -s -i \
-H "Ocp-Apim-Subscription-Key: ${SUBSCRIPTION_KEY}" \
-H "Ocp-Apim-Trace: true" \
--max-time 70 \
"${APIM_HOST}${API_PATH}")
TRACE_URL=$(echo "$TRACE_RESPONSE" | grep -i 'Ocp-Apim-Trace-Location:' | awk '{print $2}' | tr -d '\r')
if [ -n "$TRACE_URL" ]; then
echo "[INFO] Fetching trace from: $TRACE_URL"
curl -s "$TRACE_URL" | jq '.traceEntries.inbound, .traceEntries.backend, .traceEntries.outbound | .[] | {source,timestamp,elapsed,data}' 2>/dev/null
else
echo "[WARN] No trace URL in response — tracing may be disabled or subscription lacks trace permission"
fi
echo ""
echo "=== 5. Query Application Insights for slow/failed requests (last 1 hour) ==="
APP_INSIGHTS_NAME="your-app-insights"
az monitor app-insights query \
--app "$APP_INSIGHTS_NAME" \
--resource-group "$RG" \
--analytics-query "
requests
| where timestamp > ago(1h)
| where duration > 20000 or success == false
| project timestamp, name, duration, resultCode, performanceBucket
| order by duration desc
| take 20
" --output table 2>/dev/null || echo "[SKIP] App Insights query failed — check resource name and permissions"
echo ""
echo "=== 6. Check Azure Function host timeout setting ==="
FUNCTION_APP="your-function-app"
az functionapp config appsettings list \
--name "$FUNCTION_APP" \
--resource-group "$RG" \
--query "[?name=='AzureFunctionsJobHost__functionTimeout']" \
--output table 2>/dev/null || echo "[INFO] functionTimeout not set via app setting — check host.json in deployment"
echo "Done."
Error Medic Editorial
Error Medic Editorial is a team of senior DevOps, SRE, and cloud-native engineers who distill years of production incident response into actionable troubleshooting guides. The team has collectively managed Azure environments processing billions of API calls monthly across APIM, Azure Functions, App Service, and AKS.
Sources
- https://learn.microsoft.com/en-us/azure/api-management/api-management-advanced-policies#ForwardRequest
- https://learn.microsoft.com/en-us/azure/azure-functions/functions-host-json#functiontimeout
- https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-http-features#async-operation-tracking
- https://learn.microsoft.com/en-us/azure/api-management/api-management-howto-api-inspector
- https://stackoverflow.com/questions/53223535/azure-api-management-504-gateway-timeout
- https://github.com/Azure/azure-functions-host/issues/4536
- https://learn.microsoft.com/en-us/azure/load-balancer/load-balancer-tcp-idle-timeout