The starting point
A timeout tells you a clock expired. Find the last completed stage before changing the deadline, gateway or retry policy.
Map the path that actually timed out
For an HTTPS destination through an HTTP proxy, the usual path is: resolve the proxy, connect to it over TCP, obtain a successful CONNECT tunnel, complete destination TLS, send the request, then receive headers and the body. An HTTPS proxy adds its own TLS handshake before CONNECT. The two TLS connections have separate trust settings.
The proxy normally resolves the destination hostname in this setup. A fast local DNS measurement therefore says nothing about the proxy's destination lookup. SOCKS5 can resolve locally or remotely, depending on the client configuration; identify that choice before interpreting a DNS error.
Start with one approved, small GET endpoint and one gateway. Keep authentication and routing explicit. A browser navigation or application job may also wait for a connection pool, redirects, scripts or local processing; its timeout is not automatically a proxy timeout.
Capture a single attempt without a trace
This shell example requires curl 8.3 or later because curl imports the authentication variables itself. Check curl --version, including its TLS backend and features. Set PROXY_URL to a credential-free HTTP(S) gateway supplied by your provider; https://proxy.example.invalid:8443 is a deliberately nonworking illustration. Inject PROXY_USERNAME and PROXY_PASSWORD privately. This example uses Basic proxy authentication; adapt authentication only to the provider's documented method.
Save as proxy-timing.sh and run sh proxy-timing.sh. CHECK_URL defaults to https://example.com/; you can substitute a small HTTPS endpoint that you are authorized to test. Use URLs without embedded credentials or sensitive parameters. The script discards the body, prints selected numeric results and preserves curl's exit status. It does not follow redirects or retry. Do not run it with shell tracing enabled. --disable stays first to ignore curlrc settings, and --noproxy with an empty value prevents an inherited NO_PROXY rule from bypassing the selected gateway.
: "${PROXY_URL:?Set a credential-free HTTP(S) proxy URL}"
CHECK_URL=${CHECK_URL:-https://example.com/}
case "$PROXY_URL" in
http://*|https://*) ;;
*) printf '%s\n' 'PROXY_URL must use http:// or https://' >&2; exit 2 ;;
esac
case "$CHECK_URL" in
https://*) ;;
*) printf '%s\n' 'CHECK_URL must use https://' >&2; exit 2 ;;
esac
case "$PROXY_URL $CHECK_URL" in
*'@'*|*'?'*|*'#'*)
printf '%s\n' 'Use URLs without credentials, queries or fragments' >&2
exit 2 ;;
esac
curl --disable --silent --fail --http1.1 \
--noproxy '' --proxy "$PROXY_URL" --proxy-basic \
--variable %PROXY_USERNAME --variable %PROXY_PASSWORD \
--expand-proxy-user '{{PROXY_USERNAME}}:{{PROXY_PASSWORD}}' \
--connect-timeout 5 --max-time 15 --retry 0 \
--output /dev/null \
--write-out 'exit=%{exitcode} http=%{http_code} tunnel=%{http_connect} dns=%{time_namelookup} tcp=%{time_connect} tls=%{time_appconnect} ready=%{time_pretransfer} first=%{time_starttransfer} total=%{time_total} bytes=%{size_download}\n' \
"$CHECK_URL"Read milestones, not independent timers
The time fields are seconds measured from the start of the transfer. They are not separate durations to add together. This example starts a fresh curl process and asks for HTTP/1.1 to make one request easier to inspect. Reused connections, multiplexing, proxy chains and followed redirects need additional interpretation. A zero field can reflect rounding, a stage not reached or a stage that did not apply; it does not prove an instantaneous operation.
Treat differences as clues only when both milestones completed in this simple request. For example, tls minus tcp includes the CONNECT exchange and destination handshake for an HTTP proxy; it is not a measurement of destination TLS alone. first minus ready includes sending the request and waiting across the network and server. It does not isolate the destination's CPU time.
- dns = time_namelookup: completion of the client-side lookup. With this proxy configuration, the relevant hostname is the gateway, not the destination's proxy-side lookup.
- tcp = time_connect: the connection to the proxy completed. This does not establish that the tunnel or destination connection works.
- tls = time_appconnect: TLS setup completed. ready = time_pretransfer: protocol setup reached the point where the transfer can begin. An HTTPS proxy introduces another handshake; these fields do not provide separate timings for every hop.
- first = time_starttransfer: first response byte, including preceding setup. Interpret it alongside tunnel and http; a received proxy response is not evidence of destination success.
- total = time_total and bytes = size_download: elapsed transfer time and downloaded body bytes. An HTTP 200 with exit 28 can still be an incomplete download.
Follow the last completed stage
Use the following order for the single-request example. Stop at the first unresolved stage. The suggested checks narrow the investigation; timings alone cannot identify which machine dropped a packet or explain a provider's internal queue.
- No proxy address: exit 5 means curl could not resolve the proxy. Verify the gateway spelling and the resolver available inside the actual container or service. Exit 6 concerns a hostname curl tried to resolve locally; revisit routing and DNS mode before blaming proxy-side destination DNS.
- No TCP connection: exit 7 indicates connection failure; exit 28 before tcp completes can indicate the connection budget expired. Check the configured port, outbound route and firewall from the same runtime. An immediate refusal and a silent network drop call for different checks.
- TCP completed, tunnel still 000: there is no usable CONNECT response recorded. For an HTTPS proxy, its own TLS setup may be the unfinished stage. For an HTTP proxy, investigate CONNECT negotiation, gateway availability and the allowed destination port. Provider diagnostics are needed to separate its destination DNS, connect and policy failures.
- tunnel is 407: follow the proxy authentication guide. A larger timeout will not correct credentials. Other non-2xx CONNECT responses require checking proxy policy and the provider's documented error meaning; they are not destination HTTP statuses.
- tunnel is 200, destination TLS unfinished: the proxy accepted the tunnel, but HTTPS setup did not complete. Exit 35 indicates a TLS handshake failure and exit 60 a certificate verification failure. Check destination name, approved CA trust and clock; keep certificate verification enabled. A timeout here can also arise from a stalled tunnel path.
- TLS and ready completed, http is 000: investigate the wait for a destination response, including the remote application and path back through the proxy. Check an approved control endpoint through the same gateway, then the original endpoint again; change one variable at a time.
- http is 200, bytes stopped and exit is 28: headers arrived, but the body did not finish within the limit. Compare expected response size and transfer progress. Raising the connect timeout does not address this stage.
- http is 504: an HTTP gateway reported its own upstream timeout. With --fail the example normally exits 22. This differs from curl exit 28, where a client-side time limit expired; investigate which gateway generated the response before changing the client's deadline.
Give each attempt a share of the job deadline
In curl, --connect-timeout covers connection establishment, including DNS and required protocol negotiations; it is not only a TCP timer. --max-time covers the entire transfer, including connection setup and body transfer. The connection budget sits inside the transfer budget. The example's five and fifteen seconds are starting values for a small diagnostic, not service guarantees or universal production defaults.
An application still needs an overall deadline covering pool waits, attempts, backoff, body handling and cleanup. Work backwards from the caller's limit. For an illustrative twenty-second job, you might reserve two seconds for local work, allow an eight-second initial attempt, wait one second, and permit at most an eight-second retry if sufficient time remains. Use a monotonic clock and cap each next attempt by the remaining budget; do not reset the job deadline when retrying.
A socket read timeout generally limits an interval waiting for data, not the complete job. Slow progress may repeatedly avoid it. curl's --speed-limit with --speed-time can abort persistently slow transfers, but that is a minimum-throughput policy, not a precise replacement for an application's read-idle timeout. Long polling and streaming need a policy matched to their expected pauses, plus cancellation and an overall lifetime where appropriate.
Retry only when repetition is safe and useful
The baseline deliberately has no retries. After locating the failure, a bounded retry can be reasonable for an approved GET or HEAD whose application semantics are safe and idempotent, and for a failure you expect to be temporary. HTTP method semantics are a starting point; an endpoint that triggers an action despite using GET needs its own review. A timeout after a write leaves the outcome uncertain. Check the operation's state or documented idempotency mechanism before resubmitting it.
For a reviewed safe GET, adding --retry 1 --retry-max-time 20 permits at most one retry for curl's supported transient conditions. It does not make twenty seconds a strict job deadline: --max-time restarts for each attempt, and an attempt started inside the retry window can finish after that window. Keep an outer deadline when the caller needs a firm limit. Respect Retry-After and stop when the required wait cannot fit; application retry loops should use bounded backoff with jitter and a concurrency limit.
- Do not repeatedly retry unchanged authentication, certificate, URL or policy errors. Correct the configuration first.
- Do not add --retry-all-errors to a general client as a timeout fix. That broadens repetition to failures whose recovery and side effects have not been reviewed.
- Do not replay a payment, form submission, message send or other state-changing request merely because its response was lost. A lost response does not prove the operation failed.
- Do not use retries to push through a block or increase load on an overloaded destination. Reduce concurrency and honor the service's limits.
What the local checks established
Reviewed on 11 September 2026 using curl 8.7.1 on macOS, with its reported SecureTransport/LibreSSL build. Controlled loopback fixtures exercised an HTTP proxy CONNECT exchange and a local HTTPS destination with an explicitly trusted test certificate. A stalled CONNECT and a stalled destination TLS handshake both exited 28 near a 0.3-second connection budget, but only the latter had tunnel=200. A separate stalled HTTPS-proxy TLS handshake also exited before CONNECT.
After TLS completed, stalled headers and a stalled body instead reached a 1.2-second total budget. The body case retained http=200 and one downloaded byte. A supplied HTTP 504 returned exit 22. A delayed CONNECT increased tls minus tcp, confirming that this difference includes tunnel setup. A retry fixture with a two-second retry window and 0.7-second attempts ran for about 2.4 seconds, confirming that the retry window alone is not a strict total deadline. Its final total field described the last attempt, so a retrying job also needs an outer elapsed-time measurement.
These fixtures establish client behavior under controlled failures. They do not benchmark a provider, exercise real DNS outages, validate every curl build or prove how HTTP/2, SOCKS or a multi-hop proxy behaves. Keep the runtime version, stage, statuses, timings, byte count and attempt count with the incident. Share only sanitized details; destination URLs, headers and traces can contain private data. The examples do not establish ipvolt service availability or gateway support.
From reading to doing
Before you ship
- Record the actual runtime, proxy scheme, destination scheme and DNS mode.
- Capture one explicit attempt before adding redirects, concurrency or retries.
- Use status codes and completed timing milestones together to choose the next check.
- Keep connection and transfer budgets inside an overall job deadline.
- Retry only approved safe, idempotent work within the remaining budget.
Sources & further reading
Technical references used for this guide. Check the documentation for your installed version and your provider’s supported configuration.
- Everything curl: proxy connections and DNS responsibility
- curl manual: variables, numeric output and retry-window limits
- libcurl: connection-completion timing
- libcurl: TLS-completion timing
- libcurl: pre-transfer timing
- libcurl: time to first response byte
- Everything curl: exit-code meanings
- libcurl: connection timeout within the total timeout
- libcurl: average-speed timeout policy
- Requests: read timeouts are not whole-download limits
- Everything curl: retry behavior
- RFC 9110: idempotent retry semantics and gateway statuses