gRPC Health Probe vs. HTTP Liveness Probe: Probe Type Mismatch¶
Status: Published
Experiment completed with real data on 2026-05-04.
1. Question¶
Container Apps supports liveness, readiness, and startup probes. For a server listening on a port that does not speak HTTP/1.1, what happens when an httpGet liveness probe is configured on that port? Does the probe fail and cause repeated container restarts? Is tcpSocket a valid workaround?
2. Why this matters¶
A gRPC server typically does not expose HTTP/1.1 endpoints. When a Container Apps liveness probe is configured as httpGet pointing at a gRPC port (or any non-HTTP port), the probe sends an HTTP/1.1 GET request to a port that does not respond to HTTP/1.1 — the connection fails or returns an unexpected response. Container Apps marks the probe as failed and restarts the container. This causes a continuous restart loop even though the server is healthy, which appears identical to an OOM kill or application crash in logs.
3. Customer symptom¶
"Container restarts every 30 seconds for no apparent reason" or "Liveness probe keeps failing even though the service is responding to requests" or "Container is constantly restarting — no errors in application logs."
4. Hypothesis¶
- H1: An
httpGetliveness probe targeting a port that does not respond to HTTP/1.1 (e.g., wrong port, gRPC port) fails. Container Apps logsProbeFailed | Container failed liveness probe, will be restartedand terminates the container with reasonProbeFailure. - H2: A
tcpSocketprobe on the same port succeeds because TCP connection establishment succeeds (the server is listening). The container stays healthy. - H3: Container Apps supports a
grpcprobe type (distinct fromhttpGetandtcpSocket) for gRPC health checking protocol.
5. Environment¶
| Parameter | Value |
|---|---|
| Service | Azure Container Apps |
| SKU / Plan | Consumption |
| Region | Korea Central |
| App name | aca-diag-batch |
| Runtime | Python 3.11 / gunicorn (HTTP server on port 8000) |
| Date tested | 2026-05-04 |
6. Variables¶
Experiment type: Health probes / Protocol
Controlled:
- Python/gunicorn Flask app listening on port 8000 (HTTP/1.1 + HTTP/2)
- Three probe configurations tested sequentially:
- P1:
httpGet /health:8000— correct (HTTP server, correct path) - P2:
httpGet /health:9999— wrong port (nothing listening) — simulates probe on gRPC-only port - P3:
tcpSocket :8000— TCP connection check only
Observed:
- Container health state (Healthy / Unhealthy)
- System log events (
ProbeFailed,ContainerTerminated,RevisionReady) - HTTP response from app during probe failure cycle
7. Instrumentation¶
- YAML-based probe configuration via
az containerapp update --yaml az containerapp logs show --type system— system events for probe failuresaz containerapp revision list --query "properties.healthState"— revision health state
8. Procedure¶
- P1: Set
httpGet /health:8000; verify container stays Healthy. - P2: Set
httpGet /health:9999(nothing listening); waitfailureThreshold × periodSeconds= 30s; observeProbeFailed→ContainerTerminated. - P3: Set
tcpSocket :8000; observe container stays Healthy (TCP connect succeeds). - Verify
grpcprobe type is available in CLI help.
9. Expected signal¶
- P1: Container Healthy; no probe events.
- P2: After 3 failures × 10s = 30s,
ProbeFailed | will be restarted;ContainerTerminated | reason: ProbeFailure. - P3: Container Healthy; TCP connection to port 8000 succeeds regardless of HTTP protocol layer.
10. Results¶
P1: HTTP probe on correct port — container stays healthy¶
```yaml probes: - type: Liveness httpGet: path: /health port: 8000 scheme: HTTP initialDelaySeconds: 5 periodSeconds: 10 failureThreshold: 3
```text
```text az containerapp revision list ... --query "[].healthState" → "Healthy"
```text No probe failure events in system logs.
P2: HTTP probe on wrong port (port 9999 — nothing listening)¶
```yaml probes: - type: Liveness httpGet: path: /health port: 9999 # Nothing listening scheme: HTTP initialDelaySeconds: 5 periodSeconds: 10 failureThreshold: 3
```text System log events after ~30s:
```text 05:53:27 | ProbeFailed | Container aca-diag-batch failed liveness probe, will be restarted 05:53:27 | ContainerTerminated | Container 'aca-diag-batch' was terminated with exit code '' and reason 'ProbeFailure'
05:54:03 | ProbeFailed | Container aca-diag-batch failed liveness probe, will be restarted 05:54:03 | ContainerTerminated | Container 'aca-diag-batch' was terminated with exit code '' and reason 'ProbeFailure'
```text The container restarts on each probe failure cycle. The restart loop continues indefinitely — the container never reaches a stable Healthy state.
Key finding
The system log reason is ProbeFailure — distinct from OOMKilled or application crash. This is the diagnostic signal that identifies probe misconfiguration vs. actual container failure.
P3: tcpSocket probe on port 8000 — container stays healthy¶
```yaml probes: - type: Liveness tcpSocket: port: 8000 initialDelaySeconds: 5 periodSeconds: 10 failureThreshold: 3
```text
```text 05:54:56 | RevisionReady | Successfully provisioned revision 'aca-diag-batch--0000026'
``text
Container transitions to Healthy. NoProbeFailed` events. TCP connection to port 8000 succeeds — the probe does not inspect HTTP response codes.
H3: gRPC probe type — CLI verification¶
```bash az containerapp create --help | grep -i "grpc|probe" → values: grpc, http. grpc servers
``text
Thegrpcprobe type is supported in Container Apps. It uses the gRPC Health Checking Protocol (grpc.health.v1.Health/Check`) to verify gRPC server health without requiring an HTTP endpoint.
11. Interpretation¶
- Measured: H1 is confirmed. An
httpGetprobe on a port that does not respond to HTTP/1.1 fails withProbeFailedevents, causingContainerTerminated | reason: ProbeFailurerestarts. The restart loop repeats on eachperiodSecondscycle afterfailureThresholdconsecutive failures. Measured. - Measured: H2 is confirmed. A
tcpSocketprobe on port 8000 succeeds — TCP connection establishment is all that is checked. The container transitions to Healthy immediately. Measured. - Observed: H3 is confirmed. The Container Apps CLI help confirms
grpcas a supported probe type for gRPC servers. Observed (CLI help; actual gRPC probe behavior not separately tested).
12. What this proves¶
- An
httpGetprobe on a non-HTTP port (or wrong port) causes a continuous container restart loop with reasonProbeFailure. Measured. - System log event
ContainerTerminated | reason: ProbeFailureuniquely identifies probe misconfiguration as the cause — distinguishable from OOM (reason: OOMKilled) or application crash. Measured. - A
tcpSocketprobe is a valid workaround for non-HTTP servers — it verifies port reachability without inspecting application-layer responses. Measured. - Container Apps supports a
grpcprobe type for native gRPC health checking. Observed (CLI).
13. What this does NOT prove¶
- Whether the
grpcprobe type actually invokesgrpc.health.v1.Health/Checkwas not tested (no gRPC server deployed). - Whether an
httpGetprobe on an actual gRPC port (vs. a closed port) produces a different failure message was not tested. - Startup probe behavior on gRPC servers was not tested.
- Whether readiness probe failures (vs. liveness probe failures) produce different log events was not tested.
14. Support takeaway¶
When a customer reports "container restarts every 30 seconds with no application errors":
- Check system log reason:
ContainerTerminated | reason: ProbeFailure→ probe misconfiguration, not application crash.OOMKilled→ memory limit.Error→ application crash. - Verify probe port:
az containerapp show --query "properties.template.containers[0].probes". Probe port must match the port the server listens on. -
For gRPC servers: use
grpcprobe type (gRPC Health Checking Protocol) ortcpSocketas a fallback: ```yaml probes:- type: Liveness grpc: port: 50051 # OR:
- type: Liveness tcpSocket: port: 50051 ```
-
httpGetprobes require the server to respond to HTTP/1.1 GET requests with 2xx. gRPC servers, raw TCP servers, and custom binary protocol servers will failhttpGetprobes.
15. Reproduction notes¶
```bash
APP="
Export current YAML¶
az containerapp show -n $APP -g $RG -o yaml > /tmp/app.yaml
Edit: set httpGet probe on wrong port¶
probes:¶
- type: Liveness¶
httpGet: {path: /health, port: 9999, scheme: HTTP}¶
initialDelaySeconds: 5, periodSeconds: 10, failureThreshold: 3¶
az containerapp update -n $APP -g $RG --yaml /tmp/app.yaml sleep 40
Check for ProbeFailure events¶
az containerapp logs show -n $APP -g $RG --type system --tail 20 | \ grep -E "ProbeFailed|ProbeFailure|ContainerTerminated"
Expected: ProbeFailed | Container ... failed liveness probe, will be restarted¶
ContainerTerminated | reason 'ProbeFailure'¶
Fix: switch to tcpSocket on correct port¶
probes:¶
- type: Liveness¶
tcpSocket: {port: 8000}¶
az containerapp update -n $APP -g $RG --yaml /tmp/app-fixed.yaml
```text