Cloud
Azure Container Apps: diagnose outbound egress before changing code
A production runbook for qualifying Azure Container Apps outbound failures by separating DNS, UDR, NSG, NAT Gateway, firewall, identity, logs and rollback before changing the application.
An Azure Container Apps service can receive inbound traffic correctly and still fail as soon as it calls an external service or a private dependency. The symptom often arrives as a timeout, a 403, a TLS error or a queue of retries. The tempting response is to redeploy the image, raise the timeout or change an environment variable. In production, the outbound path should be proven first.
The use case is an API hosted on Container Apps that calls several dependencies: a partner API allowlisting source IPs, Azure SQL or Key Vault, an internal service behind a firewall, and a telemetry endpoint. The Container Apps environment is integrated with a VNet. Traffic may cross a UDR, Azure Firewall, NAT Gateway, Private DNS or NSG rules. The runbook aims at one practical decision: fix the network, adjust the dependency, roll back an infrastructure change, or return to application code only after the outbound path is healthy.
Map the egress path before the incident
Container Apps outbound traffic is not just “Internet from the container”. Depending on the environment, it may cross a delegated subnet, forced routing, a firewall policy, NAT Gateway, private or public DNS resolution, then the access rule on the dependency. If these pieces are not written down, each team will diagnose its own layer in parallel.
Container app
Active revision, replicas, configuration variables
Managed identity or application secret
HTTP client, SDK or connector in use
Container Apps environment
VNet integration
Dedicated subnet or workload profile
DNS used by the runtime
System and console logs
Azure routing
Effective route to Internet or private network
UDR to Azure Firewall when forced tunneling is used
NAT Gateway when stable outbound IP is expected
NSG on the subnet and intermediate networks
Called dependency
FQDN, port, protocol and expected SNI
Firewall, IP allowlist, Private Link or public endpoint
Service-side logs, correlation ID and test window
Expected decision
Path healthy, fix application
DNS incorrect, fix resolver or zone
Route/NSG/firewall blocked, fix infrastructure
Outbound IP unstable, verify NAT Gateway
Identity or secret failing, handle authentication This map forces a useful distinction: a timeout toward a partner API is not handled like a Key Vault 403, and a DNS error is not fixed by an application rollback.
Classify the symptom by failure family
Before changing a route or deployment, classify the symptom. The same outbound call can fail during DNS resolution, TCP connection, TLS negotiation, network authorization or application authentication.
Observed symptom
no such host, ENOTFOUND, NameResolutionFailure
Priority: DNS, private zone, forwarder, resolver, FQDN suffix
timeout, connection refused, no route to host
Priority: UDR, NSG, Azure Firewall, port, service availability
TLS handshake failed, certificate verify failed
Priority: SNI, called FQDN, TLS inspection, certificate chain
HTTP 403 or denied by firewall
Priority: outbound IP, allowlist, Private Link, identity, service policy
HTTP 429 or massive retries
Priority: saturated dependency, retry policy, replica scale, quotas
Error only on one revision
Priority: revision configuration, secret, variable, image or SDK This reading prevents a common bad reflex: opening the firewall too broadly because the error says “access denied”, while the managed identity or secret simply changed.
Prove DNS and TLS from the same path as the app
Diagnostics should start from a context close to the runtime. An admin workstation or CI runner outside the VNet does not prove the Container Apps path. If the application image has no debug tool, create a temporary revision or controlled Container Apps job with the same network constraints to test only resolution and connectivity.
TARGET_FQDN=api.partner.example.com
TARGET_PORT=443
getent hosts "$TARGET_FQDN" || nslookup "$TARGET_FQDN"
timeout 5 sh -c "cat </dev/null >/dev/tcp/$TARGET_FQDN/$TARGET_PORT" && echo "tcp_connect_ok=true" || echo "tcp_connect_ok=false"
openssl s_client -connect "$TARGET_FQDN:$TARGET_PORT" -servername "$TARGET_FQDN" </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer
curl -vk --connect-timeout 5 "https://$TARGET_FQDN/health" -H "x-correlation-id: aca-egress-check-$(date +%Y%m%d%H%M%S)" If the FQDN must resolve to a private address, verify the target explicitly. If the partner service expects a fixed public IP, capture the address seen from the partner side or through a diagnostic endpoint. The useful information is not only “it works”; it is “it exits through the expected path”.
Read routes, NSG and NAT without assuming the answer
When Container Apps is integrated with a VNet, the network team should be able to answer three questions: which route applies, which rule allows or blocks, and which outbound IP is presented to the dependency. NAT Gateway helps stabilize the IP, but it does not fix a UDR that sends the flow to the wrong place or a firewall rule that is too restrictive.
RG=rg-prod-apps
ENV=aca-prod-weu
SUBNET_ID="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-network-prod/providers/Microsoft.Network/virtualNetworks/vnet-prod/subnets/snet-aca-prod"
az containerapp env show --name "$ENV" --resource-group "$RG" --query '{name:name, infrastructureSubnetId:properties.infrastructureSubnetId, outboundType:properties.vnetConfiguration.outboundType}' --output table
az network vnet subnet show --ids "$SUBNET_ID" --query '{routeTable:routeTable.id, nsg:networkSecurityGroup.id, natGateway:natGateway.id, delegations:delegations[].serviceName}' --output json
# Complete with Network Watcher, firewall logs and effective route table according to the subnet design.
# The goal is to prove the next hop and the rule that allows or blocks the flow. The exact commands depend on the environment type and available network tooling. The stable part of the runbook is the evidence: route table attached, NSG attached, NAT Gateway attached or missing, firewall crossed or not, and dependency observing the right source IP.
Correlate Container Apps and network logs
During the incident, application logs alone can mislead. An SDK may collapse DNS, TLS and HTTP into a generic exception. Read Container Apps logs with network or firewall signals over the same window.
let Window = 2h;
let App = "orders-api";
let Target = "api.partner.example.com";
let Console =
ContainerAppConsoleLogs_CL
| where TimeGenerated > ago(Window)
| where ContainerAppName_s == App
| where Log_s has_any (Target, "timeout", "ENOTFOUND", "403", "TLS", "retry", "connection")
| project TimeGenerated,
Source="container-console",
App=ContainerAppName_s,
Revision=RevisionName_s,
Message=Log_s;
let System =
ContainerAppSystemLogs_CL
| where TimeGenerated > ago(Window)
| where ContainerAppName_s == App
| where Log_s has_any ("revision", "replica", "probe", "scale", "failed")
| project TimeGenerated,
Source="container-system",
App=ContainerAppName_s,
Revision=RevisionName_s,
Message=Log_s;
Console
| union System
| order by TimeGenerated desc If Azure Firewall, NSG flow logs or dependency logs are available, add a parallel query with the same correlation_id, source IP or time window. A correction decision should rely on at least two views when possible: runtime and network, or runtime and dependency.
Separate network errors from identity errors
Azure dependencies can blur the reading. A Key Vault, Storage, SQL or Service Bus call may fail because the network is closed, because DNS points to the wrong place, or because the managed identity lacks the expected permission. Opening the network to solve an RBAC error creates unnecessary security debt.
Azure dependency with 403 failure
Check first:
Called FQDN and its resolution
Expected IP or private path
Service firewall or public network access
Real identity of the Container Apps revision
Audit logs on the target service
Decision
DNS is wrong
Fix zone, record or forwarder, then retest without changing roles
Firewall blocks with correct identity
Fix allowlist, private path or NAT, then retain source IP evidence
Identity denied with healthy network
Fix RBAC or access policy, then verify no network opening was added
Only one revision affected
Compare secrets, variables, identity binding and image before rollback This matrix keeps the change minimal. It also protects rollback: returning to the previous revision is useless if the firewall policy blocks every revision.
Decide fix, hold or rollback
The runbook should end with an explicit decision, not an accumulation of workarounds. Rollback may be application, network or dependency-side depending on the evidence.
Decision: fix the network
DNS, route, NSG, firewall or NAT explains the symptom
The same test passes after correction
The dependency sees the expected source
No application change is required
Decision: fix the application or configuration
DNS, route and connectivity are proven
The error follows a revision, variable, secret or SDK version
Revision rollback restores the service
A code/configuration fix is opened with evidence
Decision: roll back infrastructure
A recent UDR, NSG, firewall policy or NAT Gateway changed the path
Impact reaches more than one application
Rollback restores connectivity without opening broadly
Logs from the window are retained
Decision: hold under watch
The flow is restored but the exact cause remains partial
Logs and probes are added
A short review is planned before the next deployment The best rollback removes the last proven change, not the change that feels fastest. If a recent UDR forces all traffic through a firewall that does not know the destination, rolling back the Container Apps revision only hides the analysis.
Conclusion
Diagnosing Azure Container Apps egress means treating outbound traffic as a complete production path. DNS, UDR, NSG, NAT Gateway, firewall, identity, runtime logs and dependency logs should be read before touching the code.
The decision then becomes operable: fix the network when the path is wrong, fix the application when the path is healthy, or roll back infrastructure when a recent change moved the exit. What matters is not only restoring the outbound call, but knowing which path it uses, which IP it presents and which evidence will prevent the incident from repeating.