mTLS Failures¶
Symptom¶
- The app never sees
X-Forwarded-Client-Cert. - Ingress returns
403whenclientCertificateMode=require. - App code reports certificate parsing or chain validation failures.
- Dapr sidecar logs show mTLS handshake or invocation failures.
- Traffic inside the environment appears unencrypted after peer encryption was expected.
flowchart TD
Start[mTLS failure] --> Edge{Edge or internal?}
Edge -->|Ingress| IngressCheck[Check clientCertificateMode and client cert send path]
Edge -->|App code| AppCheck[Parse and validate leaf certificate]
Edge -->|Dapr| DaprCheck[Review Dapr sidecar logs and App IDs]
Edge -->|Peer encryption| EnvCheck[Review managed environment encryption settings]
IngressCheck --> Fix[Apply targeted fix and retest]
AppCheck --> Fix
DaprCheck --> Fix
EnvCheck --> Fix Possible Causes¶
clientCertificateModeisignore, so ingress never forwards the header.- The client does not actually send a certificate during the TLS handshake.
- App code parses the entire XFCC header instead of the leaf
Cert=segment. - The Dapr caller uses the wrong App ID or bypasses the sidecar.
- Environment peer encryption was never enabled or was enabled in a different environment than expected.
Diagnosis Steps¶
1. Missing X-Forwarded-Client-Cert header at the app¶
Check ingress mode first:
az containerapp show \
--name "$APP_NAME" \
--resource-group "$RG" \
--query "properties.configuration.ingress.clientCertificateMode" \
--output tsv
If the value is ignore, the absence of the header is expected.
2. 403 from ingress with clientCertificateMode=require¶
Test the endpoint with and without a client certificate:
curl --include "https://${FQDN}/cert-info"
curl --include \
--cert "./client.pem" \
--key "./client.key" \
"https://${FQDN}/cert-info"
If only the second call succeeds, ingress enforcement is working and the first caller simply lacked a certificate.
3. Chain validation or parsing failure in app code¶
Look for these anti-patterns:
- Parsing the whole header instead of
Cert=. - Treating escaped newlines as literal text.
- Comparing the wrong thumbprint algorithm.
- Validating only CN when your policy actually depends on issuer or SAN.
4. Dapr sidecar mTLS handshake issues¶
Check Dapr configuration and app identity:
az containerapp show \
--name "$APP_NAME" \
--resource-group "$RG" \
--query "properties.configuration.dapr" \
--output json
az containerapp logs show \
--name "$APP_NAME" \
--resource-group "$RG" \
--type system
Confirm that:
- Dapr is enabled on both caller and callee.
- The caller uses the correct Dapr App ID.
- The app is calling
localhost:3500and not bypassing the sidecar by accident.
5. Environment-internal traffic still looks unencrypted¶
Review the managed environment:
az containerapp env show \
--name "$ENVIRONMENT_NAME" \
--resource-group "$RG" \
--query "properties.peerTrafficConfiguration" \
--output json
If the property is empty or encryption is disabled, peer encryption was never turned on for that environment.
Resolution¶
- Set
clientCertificateModetorequireoracceptwhen the app needs XFCC. - Ensure testing clients provide both
--certand--key. - Parse only the leaf
Cert=element fromX-Forwarded-Client-Cert. - Correct Dapr App IDs and force all Dapr traffic through the sidecar endpoint.
- Enable
peerTrafficConfiguration.encryption.enabledon the managed environment when direct internal traffic must be encrypted.
Prevention¶
- Keep ingress mode, certificate policy, and route ownership documented together.
- Add a
/cert-infoor equivalent diagnostics endpoint in lower environments. - Monitor Dapr system logs during rollout of new App IDs or sidecar config.
- Validate managed environment encryption settings as part of environment provisioning.
See Also¶
- Ingress Client Certificates
- mTLS Architecture in Azure Container Apps
- Service-to-Service Connectivity Failure