The timeouts supported are:

  • :connect_timeout
  • :write_timeout (since v0.21.0)
  • :read_timeout (since v0.21.0)
  • :request_timeout (since v0.21.0)
  • :operation_timeout
  • :keep_alive_timeout
  • :total_timeout

The can be passed as an extra option to the session:

HTTPX.with(timeout: { operation_timeout: 15 }).get("https://google.....")

Timeouts which expire will trigger an error of the HTTPX::TimeoutError type.

:connect_timeout (Default: 60)

The :connect_timeout covers the time it takes to establish a TCP connection, and in case it’s a TLS connection, the time it takes to establish a TLS connection.

Timeout expires raise a HTTPX::ConnectionTimeoutError exception, which subclasses HTTPX::TimeoutError.

Note: This timeout does not cover the time it takes to perform the DNS hostname resolution.

:write_timeout (no default)

The :write_timeout covers the total time it takes to fully send an HTTP request to the server.

Timeout expires raise a HTTPX::WriteTimeoutError exception.

:read_timeout (no default)

The :read_timeout covers the total time it takes to fully read an HTTP response from the server.

Timeout expires raise a HTTPX::ReadTimeoutError exception.

:request_timeout (no default)

The :request_timeout covers the total time it takes to send an HTTP request and read its response fromm the server.

You can use it to combine the 2 timeouts above into 1, and don’t care that much about knowing whether the request failed on writing or reading.

Timeout expires raise a HTTPX::RequestTimeoutError exception.

:operation_timeout (Default: 60)

The :operation_timeout is used when waiting for read/write interests on the TCP connection.

Contrary to the timeouts above, which are deadline oriented, :operation_timeout is used for the individual socket operations, so overall request/response turnaround time may still be quite high, given slow servers can still take advantage of it. If this is important to you, consider instead using :read_timeout or :write_timeout instead.

Timeout expires raise a HTTPX::TimeoutError exception.

:keep_alive_timeout (Default: 20)

The :keep_alive_timeout is the time after last use of connection (if the connection is persistent) that we will reuse the it for subsequent requests for the same origin. This timeout is therefore reset after every successful response. If the timeout expires when sending a request for the same origin, one of two things will happen:

  • HTTP/2: a PING frame will be sent on the connection to check availability of the connection; if still available, it will be reused, otherwise a new connection will be opened.
  • HTTP/1: a new connection will be opened.


The :total_timeout covers the time to use connections from the moment connection starts, i.e. it cover the time for all requests to be processed. If connnections are still available beyond :total_timeout, a HTTPX::TotalTimeoutError exception is raised.

Attention: if you use the :persistent plugin, the behaviour might result dodgy and unexpected. That’s because the plugin keeps the connections open as much as possible, and :total_timeout actually covers the connection lifetime, so it’s highly likely the connection lifetime will most times surpass the set timeout. Things will still “appear” to work, because the :persistent plugin retries requests at least one (to recover from network failures and abruptly closed sockets), so the “total timeout error” will be hidden away from you. But that’s considered a “quirk”, not a “feature”.


Why are read/write timeouts deadline oriented, instead of socket op timeouts, like in most other HTTP client libraries?

Most other libraries only support HTTP/1.1 , which means there’s a direct mapping between the TCP operation to the HTTP interaction (i.e. a read timeout implies one failed reading the HTTP response). This is not true anymore with HTTP/2, where successive socket reads/writes happen during a single HTTP interaction. So this would lead to confusion in some scenarios, so it was replaced by a single op timeout, :operation_timeout, applicable to both socket reads and writes.

:write_timeout, :read_timeout and :request_timeout are deadline-oriented, in that they meter the time waiting to flush the request to the server, or fully getting a response, or both. This fits the purpose of an HTTP client better, as one doesn’t need to know about which socket, if any, is being used, and it is also less exposed to slow servers of the “dripping” kind.

What about DNS timeouts?

Resolver options are passed as :resolver_options, as a hash. The right answer depends of which resolver you’re using:

System resolver

You can pass a :timeouts to the :resolver_options hash, which is documented in the ruby documentation.

Native resolver

As the native resolver reuses the same system resolver options API, the suggestion above still stands.

HTTP resolver (DoH)

As the DNS connection is an HTTP(S) connection, all of the above timeouts are to be reused.

Next: Connections