boto/boto3 · error · RetriesExceededError
Max Retries Exceeded
Error message
Max Retries Exceeded
What it means
`RetriesExceededError` is raised by `S3Transfer.download_file` when the underlying s3transfer exhausts all retry attempts (each chunk/part download keeps failing). It wraps `S3TransferRetriesExceededError` for backwards compatibility, since users historically caught boto3's exception. The inner `last_exception` carries the cause of the final failed attempt. It usually points at persistent network problems or a consistently failing request (e.g. permission denied on every retry), not transient noise.
Solutions
- Inspect `e.last_exception` to see the real failure (ClientError code or socket error) — fixing the root cause beats raising the retry cap.
- Increase the retry budget: `TransferConfig(max_request_concurrency=..., max_concurrency=...)` and, for the client, set `Config(retries={'max_attempts': 10, 'mode': 'adaptive'})`.
- Reduce per-request size to lower the chance any single part fails: `TransferConfig(multipart_threshold=..., multipart_chunksize=...)`.
- For transient network issues, wrap the call in an outer retry with backoff (e.g. `tenacity`), and verify connectivity to `*.s3.<region>.amazonaws.com`.
Example fix
# before
client.download_file('bucket', 'big', '/tmp/big') # RetriesExceededError
# after: diagnose + expand budget
from botocore.config import Config
client = boto3.client('s3', config=Config(retries={'max_attempts': 10, 'mode': 'adaptive'}))
from boto3.s3.transfer import TransferConfig
client.download_file('bucket', 'big', '/tmp/big', Config=TransferConfig(max_concurrency=20)) Defensive patterns
Strategy: retry
Validate before calling
from botocore.config import Config
from boto3.s3.transfer import TransferConfig
s3 = boto3.client('s3', config=Config(retries={'max_attempts': 10, 'mode': 'adaptive'}))
tcfg = TransferConfig(max_concurrency=20)
# inspect last_exception after a failure to decide if a retry is worthwhile Type guard
from boto3.exceptions import RetriesExceededError
def is_retries_exceeded(e) -> bool:
return isinstance(e, RetriesExceededError) Try / catch
import time
from boto3.exceptions import RetriesExceededError
attempt = 0
while True:
try:
client.download_file(bucket, key, path, Config=tcfg)
break
except RetriesExceededError as e:
attempt += 1
if attempt > 3:
raise
last = getattr(e, 'last_exception', None)
log.warning('download retry %d, last=%r', attempt, last)
time.sleep(2 ** attempt) Prevention
- Use botocore Config retries mode='adaptive' for backoff on throttling.
- Inspect last_exception before assuming retries will ever succeed.
- Verify network egress to S3 and IAM perms before retrying.
When it happens
Trigger: A `download_file` call where every attempt to fetch the object (or one of its parts in a multipart download) fails: repeated timeouts, connection resets, persistent `403 AccessDenied`, or `404 NoSuchKey` on an object that never appears during the retry window.
Common situations: Unstable/slow network (mobile, congested NAT), restrictive firewall/proxy dropping large ranged GETs, IAM permissions failing consistently, an object that was deleted mid-download, or the default retry budget being too small for a flaky link combined with a large file.
Related errors
- CRT transfer client is configured but is missing minimum…
- Either a boto3.Client or s3transfer.manager.TransferManager…
- Fileobj must implement write
- Manager cannot be provided with client, config, nor osutil…
- The following transfer config options are invalid when…
AI-assisted analysis of boto/boto3@6e10b029c1 (2026-08-11).
Data as JSON: /api/errors/97f13c304134d272.
Report an issue: GitHub.
Appendix: source
Thrown at boto3/s3/transfer.py:491
"""
if isinstance(filename, PathLike):
filename = fspath(filename)
if not isinstance(filename, str):
raise ValueError('Filename must be a string or a path-like object')
subscribers = self._get_subscribers(callback)
future = self._manager.download(
bucket, key, filename, extra_args, subscribers
)
try:
future.result()
# This is for backwards compatibility where when retries are
# exceeded we need to throw the same error from boto3 instead of
# s3transfer's built in RetriesExceededError as current users are
# catching the boto3 one instead of the s3transfer exception to do
# their own retries.
except S3TransferRetriesExceededError as e:
raise RetriesExceededError(e.last_exception)
def _get_subscribers(self, callback):
if not callback:
return None
return [ProgressCallbackInvoker(callback)]
def __enter__(self):
return self
def __exit__(self, *args):
self._manager.__exit__(*args)
class ProgressCallbackInvoker(BaseSubscriber):
"""A back-compat wrapper to invoke a provided callback via a subscriber
:param callback: A callable that takes a single positional argument for
how many bytes were transferred.View on GitHub (pinned to 6e10b029c1)