boto3 ReadTimeoutError "Read Timeout on Endpoint URL": Why Your Lambda Ran Five Times, read_timeout vs connect_timeout, Retry Modes, and the AWS CLI --cli-read-timeout Fix for Large S3 Uploads

Logeshwaran
—

botocore.exceptions.ReadTimeoutError: Read timeout on endpoint URL: "https://..." means your Python code (boto3) or the AWS CLI connected to AWS, sent the request, and then waited longer than its read timeout, 60 seconds by default, without receiving a response. It is not a network failure (that is ConnectTimeoutError) and not AWS refusing you; something was still working, or a slow link was still moving data, when your client stopped waiting. Here is the part that turns an annoyance into a real bug: boto3 retries read timeouts automatically, and its default retry mode makes up to five attempts. When the slow call is a Lambda invoke, each attempt is a new invocation. In a test against an endpoint that took 5 seconds to answer, one invoke() call with a 2-second read timeout reached the endpoint five times before boto3 gave up, so a function that sends an email or charges a card would have done it five times. The fix is to set read_timeout longer than the slowest legitimate response, and to choose the retry mode on purpose. This page covers the Lambda case, large S3 uploads with the AWS CLI (--cli-read-timeout), Lambdas inside a VPC, the retry settings that matter, and how to tell a read timeout from its lookalikes.

Jake's phone repair shop sends customers a text and an email when their repair is ready. Ethan, Jake's developer friend, built it as one Lambda function that sends the messages, called from another function with boto3. One busy Saturday the messaging service was slow, and a customer called the shop, half amused and half annoyed, to say she had received the "your phone is ready" text five times. Jake's question was the right one: "Did the computer panic and press the button five times?" Almost exactly. This page is how Ethan found out why, and stopped it.

⚡ Quick Answer

• What it means: connected, sent, then no response within read_timeout (60 s default). The work may still be running on the AWS side. Plain English.

• Lambda invoked several times: boto3 retried the timed-out invoke. Set read_timeout above the function's timeout and retries={"total_max_attempts": 1} for non-idempotent calls. Fix.

• AWS CLI on big S3 uploads: aws s3 cp ... --cli-read-timeout 0 (no timeout) or a larger value, and fewer concurrent parts on slow links. CLI fix.

• Set timeouts in boto3: Config(connect_timeout=5, read_timeout=120, retries={"mode": "standard", "total_max_attempts": 3}). Config explained.

• ConnectTimeoutError instead? That is a network path problem (often Lambda in a VPC without NAT or endpoints), not slowness. The difference.

The five-invocation test · retry modes · fixes by symptom.

New to Lambda or boto3? Our plain-English Lambda guide and our boto3 error-handling guide cover the words used here. You can follow this page without them.

🧭 NEW HERE? READ THESE FIRST

New to Lambda, boto3 or AWS networking? These five pages give you the words this one uses:

📌 Bookmark this; the timeout and retry settings block is the part you will come back for.

What "Read timeout on endpoint URL" means

Every AWS call you make from boto3 or the CLI has two clocks, both 60 seconds by default:

  • The connect timeout limits how long the client waits to open a connection to the endpoint. Exceed it and you get ConnectTimeoutError: Connect timeout on endpoint URL. Nothing reached AWS.
  • The read timeout limits how long the client waits for data on an open connection. Exceed it and you get ReadTimeoutError: Read timeout on endpoint URL. The request reached AWS; the answer did not come back in time.

That second fact is the one you need to absorb. A read timeout says nothing about whether the work happened. A Lambda function you invoked may still be running, may finish successfully a minute later, and may have sent its email; your client simply stopped listening. An S3 upload part may have been nearly complete. A slow query may still be executing. The URL in the message is the exact endpoint and path that timed out, which tells you the service and often the operation (a path ending in /functions/NAME/invocations is a Lambda invoke; one with uploadId and partNumber is an S3 multipart upload part).

Jake: "So it's like ringing a supplier, the call connects, and they put you on hold so long you hang up?"

Ethan: "Exactly. And they might still be packing your order. If you ring back and order again, you get two boxes."

The test: one invoke() call, five invocations

To see the retry behavior without risking a real function, here is a tiny fake AWS endpoint that takes 5 seconds to answer, and a boto3 Lambda client with a 2-second read timeout. The endpoint counts how many requests arrive for a single invoke() call. Run it with any recent boto3; it needs no AWS account:

# A fake "AWS endpoint" that takes 5 s to answer, so a client with a 2 s read timeout times out.
# Counts how many requests arrive per call: that is how many times your function would have run.
import threading, time, json, boto3, botocore
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from botocore.config import Config
hits = []
class H(BaseHTTPRequestHandler):
    def do_POST(self):
        hits.append(time.time()); self.rfile.read(int(self.headers.get("Content-Length", 0)))
        time.sleep(5)
        try:
            self.send_response(200); self.send_header("Content-Type", "application/json"); self.end_headers(); self.wfile.write(b"{}")
        except Exception: pass
    def log_message(self, *a): pass
srv = ThreadingHTTPServer(("127.0.0.1", 8931), H); threading.Thread(target=srv.serve_forever, daemon=True).start()
creds = dict(region_name="us-east-1", aws_access_key_id="x", aws_secret_access_key="x", endpoint_url="http://127.0.0.1:8931")
def run(label, cfg):
    hits.clear(); t = time.time()
    c = boto3.client("lambda", config=cfg, **creds)
    try: c.invoke(FunctionName="resize-photos", Payload=b"{}")
    except Exception as e: print(f"{label}: {type(e).__name__}: {e}")
    print(f"   requests received by the endpoint: {len(hits)}   wall time: {time.time()-t:.1f}s")
run("default retries (legacy), read_timeout=2", Config(read_timeout=2))
run("standard mode, read_timeout=2", Config(read_timeout=2, retries={"mode": "standard"}))
run("retries off: total_max_attempts=1, read_timeout=2", Config(read_timeout=2, retries={"total_max_attempts": 1}))
run("read_timeout=10 (longer than the work)", Config(read_timeout=10, retries={"total_max_attempts": 1}))
print("boto3", boto3.__version__, "botocore", botocore.__version__)
srv.shutdown()

The output, with boto3 1.43:

default retries (legacy), read_timeout=2: ReadTimeoutError: Read timeout on endpoint URL: "http://127.0.0.1:8931/2015-03-31/functions/resize-photos/invocations"
   requests received by the endpoint: 5   wall time: 14.2s
standard mode, read_timeout=2: ReadTimeoutError: Read timeout on endpoint URL: "..."
   requests received by the endpoint: 3   wall time: 7.6s
retries off: total_max_attempts=1, read_timeout=2: ReadTimeoutError: Read timeout on endpoint URL: "..."
   requests received by the endpoint: 1   wall time: 2.0s
   requests received by the endpoint: 1   wall time: 5.0s

Read it line by line and you see the whole problem. With boto3's default retry mode, one call produced five requests and took 14 seconds to fail. In standard mode, three. With retries switched off, one request and an immediate error. And with a read timeout longer than the work (10 seconds for a 5-second job), one request and success, which is the outcome you actually want. Against real Lambda, each of those requests is an invocation. When an error occurs, your function may be invoked multiple times, and the AWS CLI and SDKs automatically retry on client timeouts, so this is designed behavior you have to plan for, not a glitch.

Lambda: stop the duplicate invocations

Three settings, together, end your "ran five times" problem:

  1. Read timeout above the function's timeout. If the called function can legitimately run for 120 seconds, the caller's read_timeout must be longer than 120, or the caller gives up on work that is still going fine. Lambda's synchronous invocations can last up to the function's configured timeout, which can be up to 15 minutes, far past the 60-second default.
  2. Retries off, or few, for calls with side effects. An invoke that sends a message, charges a card or creates a record should not be repeated blindly. retries={"total_max_attempts": 1} turns retries off for that client.
  3. Make the function idempotent anyway. Pass an ID in the payload (the repair ticket number, an order ID) and have the function skip work it has already done for that ID. Then even a retry from somewhere else (an event source, a person clicking twice) is harmless.
import boto3, json
from botocore.config import Config

lam = boto3.client("lambda", config=Config(
    connect_timeout=5,
    read_timeout=200,                       # longer than the called function's 180 s timeout
    retries={"total_max_attempts": 1},      # no automatic re-invocation
))
resp = lam.invoke(FunctionName="notify-customer",
                  Payload=json.dumps({"ticket": "R-1042"}).encode())

If the caller does not need the answer, the cleaner design is not to wait at all: invoke asynchronously (InvocationType="Event"), which returns as soon as Lambda queues the event. Lambda then handles retries itself (up to two more attempts if the function errors), so idempotency still matters, but no client timeout can multiply the call.

When the caller is itself a Lambda function, keep its own timeout in mind too: if the caller's function timeout is shorter than the SDK's retry cycle, the caller dies first with "Task timed out," which our Lambda timeout guide covers. And if a function behind API Gateway is slow, the 29-second integration limit bites before any of this; see our API Gateway timeout guide.

Jake: "So our sending function was fine. The function calling it just kept ringing back."

Ethan: "Five times, politely. Now it waits long enough, rings once, and the sending function checks the ticket number before it sends anything."

Setting timeouts and retries in boto3, correctly

Both timeouts and the retry behavior live in one botocore.config.Config object you pass to the client:

from botocore.config import Config
import boto3

cfg = Config(
    connect_timeout=5,                                        # seconds to open a connection
    read_timeout=120,                                         # seconds to wait for a response
    retries={"mode": "standard", "total_max_attempts": 3},    # 1 try + 2 retries
)
s3 = boto3.client("s3", config=cfg)

Three details that trip people up:

  • max_attempts means two different things. In ~/.aws/config or the AWS_MAX_ATTEMPTS variable it is the total number of requests. Inside a Config object, max_attempts counts retries only, so max_attempts: 3 there means four requests. Use total_max_attempts in code; it always means total.
  • A short connect timeout is good; a short read timeout is risky. Connecting should take well under a second, so connect_timeout=5 fails fast on a broken network. The read timeout must cover your slowest legitimate response.
  • Settings can come from the profile. retry_mode and max_attempts in ~/.aws/config apply to every client using that profile unless code overrides them, which explains "it retries differently on my laptop than in production."

Legacy, standard and adaptive retry modes

ModeAttempts (default)Retries read timeouts?Notes
legacy (boto3's default)5 totalYesOlder handler; a short list of retryable errors; no retry quota
standard3 totalYesConsistent across SDKs; exponential backoff with jitter up to 20 s; retry quota stops retry storms
adaptive3 totalYesStandard plus client-side rate limiting; for one heavily throttled resource

Legacy mode exists for backward compatibility, and the advice across all the AWS SDKs is plain: if you use it, switch to standard. Standard mode also fixes the other classic legacy symptom, a client that "pauses for 60 seconds every now and then" during bulk uploads or under throttling, because legacy handles several transient errors poorly. For most code, retries={"mode": "standard"} plus sensible timeouts is the right default; add total_max_attempts: 1 only for the calls that must not repeat.

The AWS CLI and large S3 uploads: --cli-read-timeout

You will see the CLI's version of this error on big uploads over slower connections:

Read timeout on endpoint URL: "https://bucketname.s3.amazonaws.com/file.csv?uploadId=xxx&partNumber=65"

The most-read report: a 2 GB file that uploaded fine on a fast office line timed out at 20 to 30% from a home connection of 10 Mbps. The CLI splits large files into parts (multipart upload, 8 MB parts by default once a file passes 8 MB) and sends up to 10 parts at once by default. On a slow link, ten parts share the bandwidth, each crawls, and one waits past 60 seconds. Three fixes, in the order to try them:

  1. Raise or remove the read timeout for that command: --cli-read-timeout 0 means wait indefinitely, or give it a generous number such as 300. There is a matching --cli-connect-timeout.
  2. Send fewer parts at once, so each gets real bandwidth: aws configure set default.s3.max_concurrent_requests 4. The default of 10 can overwhelm a slow system and cause connection timeouts, so fewer is better on a home line.
  3. Keep the CLI current. Older versions handled slow transfers less gracefully.
aws configure set default.s3.max_concurrent_requests 4
aws s3 cp big-backup.zip s3://shop-backups/ --cli-read-timeout 300

An S3-side refusal is different: if S3 itself gives up on a slow upload, you get S3's own RequestTimeout error code ("Your socket connection to the server was not read from or written to within the timeout period"), covered in our S3 RequestTimeout guide. The fixes overlap; the cause is on the other end.

ConnectTimeoutError and EndpointConnectionError: a different problem

If your error says Connect timeout, your request never reached AWS, and raising the read timeout does nothing for you. The most common cause by far is a Lambda function attached to a VPC: it has no internet path unless its private subnets route through a NAT gateway or have VPC endpoints for the services it calls. The function connects to nothing and waits out the connect timeout, which is why such functions "time out sometimes" or always. Our Lambda-in-a-VPC guide walks through the fix. Other connect-side causes are security groups or network ACLs blocking outbound 443, a proxy, or DNS; our guide to "Could not connect to the endpoint URL" covers those.

Read timeouts inside Lambda functions have their own common cause: a call to a slow downstream (a database, a third-party API, another function) from a function whose own timeout is short. Look at the order of events in the logs: an SDK read timeout followed by retries, or the function's own "Status: timeout" first. The fix differs; the second needs a longer function timeout or a faster dependency.

Read timeouts with other services

  • DynamoDB: rare, since responses are fast; usually a symptom of a network or VPC problem. Retrying a transactional write safely needs its idempotency token; see our DynamoDB transactions guide.
  • Athena, Redshift Data API, Glue: start a query or job and poll for the result instead of waiting on one long call.
  • Bedrock and other model APIs: long generations can exceed 60 seconds; raise read_timeout to a few minutes, or use the streaming APIs, which keep data flowing.
  • S3 downloads of large objects: use the transfer manager (download_file), which splits the download into ranged parts that each finish quickly.

The ten-minute fix order

  1. Read the error: Read (reached AWS, no answer in time) or Connect (never reached AWS).
  2. Read the URL: it names the service and operation that was slow.
  3. For Lambda invokes: set read_timeout above the function's timeout, retries to 1 for side-effecting calls, and make the function idempotent.
  4. For CLI uploads: --cli-read-timeout 0 or a large value, and fewer concurrent S3 requests on slow links.
  5. For everything: switch to retries={"mode": "standard"} and use total_max_attempts, not max_attempts, in code.
  6. For connect timeouts: fix the network path (NAT gateway, VPC endpoints, security groups), not the timeout.

Fixes by symptom

You seeDo this
Lambda ran several times for one callread_timeout above the function timeout; total_max_attempts 1; idempotent function
ReadTimeoutError after exactly 60 sDefault read timeout; raise it for slow operations
aws s3 cp times out partway, slow line--cli-read-timeout 0; s3.max_concurrent_requests 4
Calls pause for a minute, then continueLegacy retry mode; switch to standard
ConnectTimeoutError from LambdaVPC without NAT or endpoints; fix the route
More retries than you configuredmax_attempts in Config counts retries; use total_max_attempts
Lambda caller dies with Task timed outCaller's function timeout shorter than the SDK's wait plus retries
S3 returns RequestTimeoutS3 gave up on a slow upload; smaller parts, fewer at once

Frequently asked questions about boto3 ReadTimeoutError

What does "Read timeout on endpoint URL" mean?

boto3 or the AWS CLI connected and sent the request, then got no response within the read timeout, 60 seconds by default. The work may still be running on the AWS side; only the client stopped waiting.

What is the default read timeout in boto3?

60 seconds, and the connect timeout is also 60 seconds. Change them with botocore.config.Config(read_timeout=..., connect_timeout=...) passed to the client.

Why was my Lambda function invoked multiple times?

The caller's read timeout expired before the function finished, and boto3 retried the invoke automatically. With the default legacy mode that can be up to five invocations for one call. Raise read_timeout and limit retries.

How do I stop boto3 from retrying a Lambda invoke?

Create the client with Config(retries={"total_max_attempts": 1}) and a read_timeout longer than the function's timeout. Also make the function idempotent with an ID in the payload.

What is the difference between ReadTimeoutError and ConnectTimeoutError?

ConnectTimeoutError means no connection was opened, usually a network path problem such as a VPC Lambda without NAT. ReadTimeoutError means the connection worked but the response was too slow.

How do I fix "Read timeout on endpoint URL" with aws s3 cp?

Add --cli-read-timeout 0 (wait indefinitely) or a larger number, and on slow connections lower s3.max_concurrent_requests from 10 to about 4 with aws configure set.

What does --cli-read-timeout 0 do?

It makes the AWS CLI wait for a response indefinitely instead of timing out after 60 seconds. --cli-connect-timeout 0 does the same for opening the connection.

What is the default retry mode in boto3?

Legacy mode, with up to five total attempts. Standard mode is the better choice for nearly all code: three attempts with exponential backoff and a retry quota: Config(retries={"mode": "standard"}).

What is the difference between max_attempts and total_max_attempts?

In a Config object, max_attempts counts retries only, so 3 means four requests. total_max_attempts always counts total requests. In ~/.aws/config, max_attempts already means total.

Does boto3 retry ReadTimeoutError?

Yes. Legacy and standard modes both retry read timeouts, which is why a slow call can be sent several times. Turn retries off for calls with side effects.

How long can a synchronous Lambda invoke take?

Up to the called function's configured timeout, which can be as long as 15 minutes. The caller's read timeout and any proxy or firewall in between must allow that long.

Why does my boto3 upload pause for 60 seconds sometimes?

A part hit the 60-second read timeout and was retried. Legacy retry mode handles several transient errors poorly; switch to standard mode and use upload_file, which splits large files into parts.

Should I set read_timeout very high everywhere?

No. Set it just above the slowest legitimate response for that client. Keep connect_timeout short, around 5 seconds, so network failures surface quickly.

Why does a Lambda in a VPC time out calling AWS services?

Usually a connect timeout: private subnets need a NAT gateway or VPC endpoints to reach AWS APIs. Raising the read timeout will not help.

How do I set timeouts for Bedrock calls in boto3?

Pass Config(read_timeout=300) or more to the bedrock-runtime client for long generations, or use the streaming APIs, which send data continuously instead of one late response.

Can I set retry mode for all boto3 scripts at once?

Yes. Add retry_mode = standard and max_attempts = 3 to a profile in ~/.aws/config, or set AWS_RETRY_MODE and AWS_MAX_ATTEMPTS. A Config object in code overrides them.

How do I see retries happening in boto3?

Enable debug logging with boto3.set_stream_logger("botocore", logging.DEBUG); each retry and its reason appears in the log. Remove it afterward, as it is very verbose.

Is ReadTimeoutError an AWS outage?

Rarely. It is almost always slow work, a slow link or a timeout set too short. Check the service health page only if many unrelated calls start timing out at once.

Ethan changed three lines: a 200-second read timeout on the caller, one attempt instead of five, and a ticket-number check at the top of the sending function. The next slow Saturday, the logs showed a few long waits and not a single repeated text. Jake asked whether the computer had stopped panicking. "It never panicked," said Ethan. "It was too polite. It kept calling back. Now it waits as long as the job takes and rings once."

📌 If you keep one line from this page

A read timeout means you stopped waiting, not that the work stopped, and boto3 retries it by default: set read_timeout longer than the slowest real response, turn retries off for anything with side effects, and make the receiving side idempotent.

60 s default · legacy = 5 attempts · standard = 3 · total_max_attempts in code · CLI: --cli-read-timeout 0 · Connect timeout = network path.

Revision note. Written October 16, 2026, with the retry counts measured against a deliberately slow test endpoint. "Patience is a virtue," the proverb says; for an SDK, so is knowing exactly how long to be patient.

Related