How to fix Health check timed out in Docker

DockerINTERMEDIATEMEDIUMUpdated Dec 6, 2025· 297 views

This error occurs when Docker's health check command takes longer to execute than the configured timeout duration, causing Docker to consider the check as failed. Common fixes include increasing the timeout value, optimizing the health check command, and addressing underlying performance issues.

Quick fix

Fixes Health check timed out

# View container health status
docker ps

# Get detailed health check information
docker inspect --format='{{json .State.Health}}' <container_name> | jq

# View the last 5 health check results
docker inspect <container_name> | jq '.[0].State.Health.Log[-5:]'
  1. Inspect the health check timeout details
  2. Increase the timeout value
  3. Optimize the health check command
  4. Test health check timing manually
docker — error
# View container health status
docker ps
# Get detailed health check information
docker inspect --format='{{json .State.Health}}' <container_name> | jq
# View the last 5 health check results
docker inspect <container_name> | jq '.[0].State.Health.Log[-5:]'
Health check timed out

What this error means

The "Health check timed out" error in Docker indicates that the HEALTHCHECK command defined in your Dockerfile or docker-compose.yml did not complete within the allowed time. By default, Docker gives a health check 30 seconds to complete, and if the command hasn't returned an exit code by then, Docker treats it as a failure. This is different from the health check command returning an error - in a timeout situation, the command is still running when Docker decides to mark it as failed. After the configured number of consecutive timeouts (retries), Docker marks the container as "unhealthy." Timeouts commonly occur when: 1. The application inside the container is slow to respond due to high load or resource constraints 2. The health check command itself is slow (e.g., complex database queries) 3. Network latency between the health check and the target service 4. The container is severely resource-constrained (low memory, CPU throttling) 5. The default timeout (30s) is too short for your specific application

Typical symptoms

  • Container shows '(unhealthy)' status in docker ps output after waiting
  • docker inspect shows health check logs with 'OOMKilled' or no output at all
  • Container health check takes longer than expected to complete
  • Health checks succeed manually but fail when run by Docker
  • Services depending on health check hang indefinitely during startup
  • docker-compose up hangs waiting for services to become healthy
  • Container CPU/memory usage spikes during health check periods

Common causes

  1. Default timeout value (30s) is too short for slow-starting applications
  2. Health check command performs resource-intensive operations (database queries, API calls)
  3. Container is resource-constrained with low memory or CPU limits
  4. Network latency or DNS resolution delays within the container
  5. Application under heavy load cannot respond to health checks in time
  6. Health check tool (curl, wget) is slow to establish connections
  7. Disk I/O bottlenecks causing slow response times
  8. Health check endpoint performs synchronous operations that block

How to fix "Health check timed out"

1Inspect the health check timeout details

First, check the current health status and identify if timeouts are occurring:

bash
# View container health status
docker ps

# Get detailed health check information
docker inspect --format='{{json .State.Health}}' <container_name> | jq

# View the last 5 health check results
docker inspect <container_name> | jq '.[0].State.Health.Log[-5:]'

Look for health check entries where the output is empty or truncated, which indicates a timeout. You can also check the time difference between "Start" and "End" timestamps:

json
{
  "Status": "unhealthy",
  "FailingStreak": 3,
  "Log": [
    {
      "Start": "2024-01-15T10:00:00.000Z",
      "End": "2024-01-15T10:00:30.000Z",
      "ExitCode": -1,
      "Output": ""
    }
  ]
}

An ExitCode of -1 with empty output typically indicates a timeout.

2Increase the timeout value

The most direct fix is to increase the health check timeout to accommodate slower responses:

In Dockerfile:

dockerfile
HEALTHCHECK --interval=30s --timeout=60s --start-period=60s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1

In docker-compose.yml:

yaml
services:
  api:
    image: my-api:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 60s      # Increased from default 30s
      start_period: 60s
      retries: 3

Key parameters explained:
- timeout: Maximum time Docker waits for the health check to complete (default: 30s)
- interval: Time between health checks (default: 30s)
- start_period: Grace period during startup where failures don't count (default: 0s)
- retries: Consecutive failures before marking unhealthy (default: 3)

Recommended timeout values:
- Simple HTTP ping: 5-10 seconds
- Database connectivity check: 10-30 seconds
- Complex application check: 30-60 seconds
- Heavy initialization apps: 60-120 seconds

3Optimize the health check command

A slow health check command can cause timeouts. Optimize it for speed:

Use connection timeout flags:

dockerfile
# Add connect timeout to curl (5 second connection timeout)
HEALTHCHECK --timeout=30s CMD curl -f --connect-timeout 5 http://localhost:8080/health || exit 1

# Add max-time for total request timeout
HEALTHCHECK --timeout=30s CMD curl -f --connect-timeout 5 --max-time 10 http://localhost:8080/health || exit 1

Use lightweight alternatives:

dockerfile
# wget is often faster than curl
HEALTHCHECK CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1

# TCP port check (fastest, no HTTP)
HEALTHCHECK CMD nc -z localhost 8080 || exit 1

# For bash environments
HEALTHCHECK CMD bash -c 'echo > /dev/tcp/localhost/8080' || exit 1

Avoid slow health check patterns:

dockerfile
# BAD: Complex database query
HEALTHCHECK CMD psql -c "SELECT count(*) FROM large_table" || exit 1

# GOOD: Simple connection check
HEALTHCHECK CMD pg_isready -U postgres || exit 1

# BAD: Full application endpoint with heavy processing
HEALTHCHECK CMD curl http://localhost:8080/api/complex-status || exit 1

# GOOD: Dedicated lightweight health endpoint
HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1
4Test health check timing manually

Measure how long your health check actually takes to complete:

bash
# Time the health check command manually
docker exec <container_name> time curl -f http://localhost:8080/health

# Or use bash timing
docker exec <container_name> sh -c 'start=$(date +%s); curl -f http://localhost:8080/health; echo "Duration: $(($(date +%s) - start)) seconds"'

Test under various conditions:

bash
# During container startup
docker run -d --name test myimage && \
  for i in {1..10}; do \
    docker exec test curl -f http://localhost:8080/health 2>&1; \
    sleep 5; \
  done

# Under load
docker exec <container_name> sh -c 'while true; do curl -f http://localhost:8080/health; done'

If the health check takes 25+ seconds under normal conditions, increase your timeout to at least 2x that value.

5Check container resource constraints

Resource-constrained containers may timeout during health checks:

bash
# Check container resource usage
docker stats <container_name>

# Check container resource limits
docker inspect <container_name> --format='{{json .HostConfig.Memory}} {{json .HostConfig.CpuShares}}'

If memory-constrained, increase limits:

yaml
services:
  api:
    image: my-api:latest
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '0.5'
        reservations:
          memory: 256M
          cpus: '0.25'

Or using docker run:

bash
docker run -d --memory=512m --cpus=0.5 myimage

Check for OOM issues:

bash
docker inspect <container_name> | jq '.[0].State.OOMKilled'

If your container is being OOM killed or is heavily swapping, health checks will be slow or timeout.

6Create a dedicated lightweight health endpoint

Design your health endpoint to respond quickly without heavy processing:

Node.js/Express example:

javascript
// Fast health endpoint - no DB calls, no external services
app.get('/health', (req, res) => {
  res.status(200).send('OK');
});

// Separate readiness endpoint for deeper checks
app.get('/ready', async (req, res) => {
  try {
    await db.query('SELECT 1');
    res.status(200).send('Ready');
  } catch (e) {
    res.status(503).send('Not Ready');
  }
});

Python/Flask example:

python
@app.route('/health')
def health():
    # Immediate response, no blocking calls
    return 'OK', 200

@app.route('/ready')
def ready():
    # Deeper check for readiness
    try:
        db.session.execute('SELECT 1')
        return 'Ready', 200
    except Exception:
        return 'Not Ready', 503

Use the lightweight endpoint for health checks:

dockerfile
# Use fast /health, not slow /ready
HEALTHCHECK --timeout=10s CMD curl -f http://localhost:8080/health || exit 1

This pattern separates:
- Liveness (/health): Is the process running? (fast)
- Readiness (/ready): Can the service handle requests? (can be slower)

7Handle slow-starting applications

For applications that take a long time to start, use the start_period parameter:

dockerfile
HEALTHCHECK --interval=30s --timeout=30s --start-period=120s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1

In docker-compose.yml:

yaml
services:
  api:
    image: my-api:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 30s
      start_period: 120s  # Wait 2 minutes before counting failures
      retries: 3

The start_period provides a grace period during which:
- Health checks still run
- Failures don't count toward the retry limit
- Once a check succeeds, the start period ends
- Container starts as "starting" status, not "unhealthy"

For Java/JVM applications (known for slow starts):

yaml
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
  interval: 30s
  timeout: 30s
  start_period: 180s  # 3 minutes for JVM warmup
  retries: 5

Docker Engine 25.0+ adds start_interval:

dockerfile
HEALTHCHECK --start-period=60s --start-interval=5s --interval=30s --timeout=10s \
  CMD curl -f http://localhost:8080/health || exit 1

The start_interval runs health checks more frequently during the start period, allowing faster transition to healthy state.

Advanced notes

Understanding timeout vs interval behavior: The timeout value determines how long Docker waits for a single health check to complete. If the check takes longer than this, Docker terminates it and considers it failed. The interval starts counting after the previous check completes (or times out).

Formula for maximum time to unhealthy status:

bash
Time to unhealthy = start_period + (retries * (interval + timeout))

With defaults (30s interval, 30s timeout, 0s start_period, 3 retries):

bash
Maximum time = 0 + (3 * (30 + 30)) = 180 seconds = 3 minutes

Health check in high-availability setups: In Docker Swarm or Kubernetes, health check timeouts trigger container replacement:

yaml
# Docker Swarm service with health-aware deployment
services:
  api:
    deploy:
      replicas: 3
      update_config:
        failure_action: rollback
    healthcheck:
      test: curl -f http://localhost:8080/health
      interval: 30s
      timeout: 10s
      retries: 3

Debugging intermittent timeouts: If health checks timeout sporadically:

1. Check for garbage collection pauses (JVM):

bash
docker logs <container_name> 2>&1 | grep -i "gc pause"

2. Monitor system load during timeouts:

bash
docker stats --no-stream <container_name>

3. Check for network issues:

bash
docker exec <container_name> ping -c 3 localhost

Override health check timeout at runtime: You can override health check settings without rebuilding the image:

bash
# docker run override
docker run --health-cmd "curl -f http://localhost:8080/health" \
  --health-interval=30s \
  --health-timeout=60s \
  --health-retries=3 \
  --health-start-period=60s \
  myimage

# docker-compose override (no need to change Dockerfile)
services:
  api:
    healthcheck:
      test: curl -f http://localhost:8080/health
      timeout: 60s  # Override Dockerfile timeout

Disable health check for debugging:

bash
docker run --health-cmd="true" myimage  # Always healthy
docker run --no-healthcheck myimage     # Disable entirely

Health check timeout vs HTTP timeout: When using curl or wget, set the HTTP timeout lower than the Docker health check timeout:

dockerfile
# Docker timeout: 30s, curl max-time: 20s
# This ensures curl returns an error before Docker times out
HEALTHCHECK --timeout=30s CMD curl -f --max-time 20 http://localhost:8080/health || exit 1

This gives you a proper error message instead of just a timeout with no output.

Frequently asked questions

What does "Health check timed out" mean?
yml did not complete within the allowed time. By default, Docker gives a health check 30 seconds to complete, and if the command hasn't returned an exit code by then, Docker treats it as a failure. This is different from the health check command returning an error - in a timeout situation, the command is still running when Docker decides to mark it as failed.
How do I fix "Health check timed out"?
This error occurs when Docker's health check command takes longer to execute than the configured timeout duration, causing Docker to consider the check as failed. Common fixes include increasing the timeout value, optimizing the health check command, and addressing underlying performance issues.
What causes "Health check timed out"?
Default timeout value (30s) is too short for slow-starting applications Health check command performs resource-intensive operations (database queries, API calls) Container is resource-constrained with low memory or CPU limits
Is the "Health check timed out" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading