Explicit timeout values keep cURL from waiting indefinitely on unreachable hosts or slow responses. That matters in shell scripts, CI jobs, health checks, and ad-hoc troubleshooting, where a stalled request is often worse than a fast, clearly handled failure.

The main timeout switches cover different phases of the transfer. Option --connect-timeout limits the connection phase, including name resolution and the TCP plus TLS or QUIC handshake, while --max-time limits how long the full transfer may take. Both options accept decimal seconds, so they can be tuned more precisely than coarse whole-second defaults.

A timeout from cURL returns exit status 28, which makes it practical to branch cleanly in scripts. Set the per-request limits slightly above normal response time, and if you also retry transient failures, keep the overall retry budget separate with --retry-max-time so repeated attempts do not hide a slow or failing dependency. The sample hosts, paths, and file names below stay masked while preserving the shape of a normal API health check and a slower reporting endpoint.

Steps to handle timeouts in cURL:

  1. Verify the active cURL build exposes the timeout switches you plan to use.
    $ curl --help all | grep -E -- '--connect-timeout|--max-time|--retry-max-time'
         --connect-timeout <seconds>     Maximum time allowed to connect
     -m, --max-time <seconds>            Maximum time allowed for transfer
         --retry-max-time <seconds>      Retry only within this period

    The help text can vary slightly by package build, but the option names stay stable across current releases.

  2. Limit the connection phase so an unreachable host fails quickly instead of waiting for the operating system's longer network timeout.
    $ curl --connect-timeout 2 --silent --show-error http://192.0.2.200/
    curl: (28) Failed to connect to 192.0.2.200 port 80 after 2001 ms: Timeout was reached

    Option --connect-timeout stops the request if DNS lookup plus the TCP and TLS or QUIC handshake cannot finish inside the configured budget.

  3. Cap the whole request duration with --max-time so a slow response cannot block the terminal or script indefinitely.
    $ curl --max-time 2 --silent --show-error https://api.example.net/reports/monthly?profile=slow-response
    curl: (28) Operation timed out after 2001 milliseconds with 0 bytes received

    Set --max-time high enough for normal traffic, because a limit below the service's usual response time will abort healthy requests too.

  4. Combine connection and total timeouts, then print machine-readable status fields when the command is used inside automation.
    $ curl --connect-timeout 2 --max-time 4 --silent --show-error \
      --output monthly-report.json \
      --write-out "exit_code=%{exitcode}\ntime_total=%{time_total}\n" \
      https://api.example.net/reports/monthly?profile=slow-response
    curl: (28) Operation timed out after 4006 milliseconds with 0 bytes received
    exit_code=28
    time_total=4.006766

    Send the body to a file such as monthly-report.json or to /dev/null when the script only needs a reliable exit code and elapsed-time metric.

  5. Verify the same timeout profile still allows a healthy endpoint to finish normally.
    $ curl --connect-timeout 2 --max-time 5 --silent --show-error \
      --output /dev/null \
      --write-out "http_code=%{http_code}\nexit_code=%{exitcode}\ntime_total=%{time_total}\n" \
      https://api.example.net/health
    http_code=200
    exit_code=0
    time_total=0.301456

    A successful check keeps exit_code at 0 and returns before the configured --max-time ceiling with enough margin for routine variation.

  6. Branch on exit status 28 so timeout failures can be retried, logged, or escalated separately from other cURL errors.
    $ curl --connect-timeout 2 --max-time 4 --silent --show-error \
      https://api.example.net/reports/monthly?profile=slow-response > monthly-report.json
    curl: (28) Operation timed out after 4006 milliseconds with 0 bytes received
    $ rc=$?
    $ [ "$rc" -eq 28 ] && echo "request_status=timeout"
    request_status=timeout

    For idempotent requests, pair this check with --retry and --retry-max-time so transient timeout failures can be replayed without letting the whole retry loop run forever.