How to8 min readSeptember 22, 2026

How to upload files to Amazon S3: console, AWS CLI, SDK or a workspace

The four ways to get a file into an S3 bucket, the size limits that decide between them, and the transfer defaults the AWS CLI and SDKs apply on your behalf.

JeVaughn Ferguson
Founder, developer
The short version

Let the file size choose the mechanism and let the tooling handle multipart: console up to 160 GB for one-off human uploads, the CLI or an SDK transfer manager for everything larger or repeated. Then add the lifecycle rule that deletes incomplete uploads, and decide separately how people without AWS identities are meant to contribute files.

Uploading a file to Amazon S3 looks like one job with one answer. It is really four jobs: a person dragging a file into a browser, an engineer running one command, an application calling an SDK, and a colleague who should never see the AWS console at all. Each has a different tool, and the size of the file decides more than you would expect.

This guide covers the S3 console, the AWS CLI, the AWS SDKs, and how to let people upload without giving them AWS identities. Every limit and default below comes from AWS documentation, linked at the end.

The size limits that pick the tool for you

Three numbers do most of the deciding. A single PUT request can carry an object up to 5 GB. The Amazon S3 console will upload a single file up to 160 GB. Multipart upload, available through the CLI, the SDKs and the REST API, raises the ceiling much further: the multipart limits table gives a maximum object size of 48.8 TiB, with up to 10,000 parts of 5 MiB to 5 GiB each and no minimum size on the last part.

AWS recommends multipart upload once an object reaches around 100 MB rather than sending it in one operation. You rarely have to build that yourself, because the CLI and the high-level SDK helpers switch to multipart on their own once a file crosses a threshold. The numbers still matter, because they explain why a 200 GB file cannot go through the console and why a hand-written PutObject call fails at 5 GB.

  • Under 5 GB: every method works. Choose on convenience, not capability.
  • Up to 160 GB, done once, by a person: the console is fine.
  • Over 5 GB from a script or an application: multipart upload, which in practice means letting the CLI or an SDK transfer manager handle it.
  • Past 5 TB: AWS points to the S3 Transfer Manager in the AWS CLI or the Java and Python SDKs, ideally on the AWS Common Runtime (CRT).

The AWS Management Console: right for one file, wrong for a team

Open the bucket, choose Upload, then drag files and folders in. Uploading a folder preserves its structure as key prefixes: a folder named images containing sample1.jpg becomes the key images/sample1.jpg, which the console then displays as sample1.jpg inside an images folder. Before you confirm, you can set the storage class, override the bucket’s default encryption, add up to ten object tags, and set system metadata such as Content-Type and Content-Disposition.

Setting Content-Type at upload time is worth the extra click. An object that arrives with a generic type downloads instead of opening when someone later tries to view it in a browser, and fixing it afterwards means copying the object over itself.

The console stops being the answer the moment the person uploading is not an AWS user. Console access means an IAM identity, a sign-in, and enough familiarity to find the right bucket among everything else in the account. For a finance or legal colleague, that is a training problem wearing an upload problem’s clothes.

The AWS CLI: cp for once, sync for everything after that

aws s3 cp copies one file or, with --recursive, a tree. aws s3 sync copies only what differs, which makes it the right command for anything you run more than once. Sync treats a local file as needing upload if its size differs from the S3 object, if its last modified time is newer than the object’s, or if no such object exists under the bucket and prefix. --size-only makes size the sole criterion, which helps when local timestamps are not trustworthy, such as after a restore or a checkout.

Run --dryrun before any sync that can remove something. --delete removes objects at the destination that no longer exist in the source, and files excluded by a filter are also excluded from deletion, which is easy to read the wrong way round when you are in a hurry.

# One file, with the content type set so it opens in a browser later
aws s3 cp report.pdf s3://amzn-s3-demo-bucket/reports/ \
  --content-type application/pdf

# See what a filtered sync would do before it does it
aws s3 sync ./exports s3://amzn-s3-demo-bucket/exports \
  --exclude "*" --include "*.csv" --dryrun

# Mirror a directory, including removals. Check the dry run first.
aws s3 sync ./exports s3://amzn-s3-demo-bucket/exports --delete

The transfer defaults worth knowing before you tune them

The CLI’s s3 settings decide when it switches to multipart and how hard it pushes. The defaults are multipart_threshold 8MB, multipart_chunksize 8MB, max_concurrent_requests 10, max_queue_size 1000 and io_chunksize 256KB, with no bandwidth cap. Raising concurrency helps on a fat, stable link and hurts on a shared or metered one, so change one value at a time and measure rather than copying a tuning snippet.

Chunk size interacts with the 10,000-part cap, and the arithmetic is worth doing once: 8 MiB parts times 10,000 parts is roughly 80 GiB. For objects well past that, set multipart_chunksize explicitly rather than assuming the default will stretch.

# ~/.aws/config — per-profile transfer settings
[profile uploads]
region = us-east-1
s3 =
  multipart_threshold = 64MB
  multipart_chunksize = 64MB
  max_concurrent_requests = 20
  max_bandwidth = 50MB/s

Incomplete multipart uploads are a bill nobody reads

Once you initiate a multipart upload, Amazon S3 keeps every part until you explicitly complete or stop the upload. There is no expiry. Throughout that time you are billed for the storage, bandwidth and requests of the upload and its parts, and only completing or stopping it frees the parts and ends the charge. Cancelled transfers, killed CI jobs and crashed workers all leave parts behind quietly.

Parts uploaded toward S3 Glacier Flexible Retrieval or Deep Archive are the exception worth knowing: they are billed as Glacier Flexible Retrieval staging storage at S3 Standard rates until the upload completes, with only the CompleteMultipartUpload request charged at the archive rate.

AWS recommends a lifecycle rule with the AbortIncompleteMultipartUpload action to clean these up automatically. Use aws s3api list-multipart-uploads to see what is already sitting in a bucket today.

{
  "Rules": [
    {
      "ID": "abort-incomplete-multipart-uploads",
      "Status": "Enabled",
      "Filter": { "Prefix": "" },
      "AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
    }
  ]
}

The AWS SDKs: let the transfer manager do the work

In boto3, upload_file and download_file are managed transfers rather than thin wrappers over PutObject. Anything larger than the multipart_threshold becomes a multipart upload automatically, split into multipart_chunksize parts and sent concurrently. The defaults are an 8,388,608-byte (8 MB) threshold and chunk size, max_concurrency of 10, and use_threads enabled. Reach for the low-level create_multipart_upload calls only when you genuinely need what they add: uploading parts over time, or starting before the final object size is known.

Pass ExtraArgs rather than fixing metadata later. And if an upload must never clobber an existing key, conditional writes are available on PutObject and CompleteMultipartUpload requests, which is a cleaner guard than a head_object check that can race.

import boto3
from boto3.s3.transfer import TransferConfig

s3 = boto3.client("s3")

# Defaults are 8 MB threshold / 8 MB chunks / 10 threads.
# Larger parts for large objects keeps you under the 10,000-part cap.
config = TransferConfig(
    multipart_threshold=64 * 1024 * 1024,
    multipart_chunksize=64 * 1024 * 1024,
    max_concurrency=10,
)

s3.upload_file(
    "quarterly-report.pdf",
    "amzn-s3-demo-bucket",
    "reports/2026-q3/quarterly-report.pdf",
    ExtraArgs={
        "ContentType": "application/pdf",
        "ContentDisposition": "inline",
    },
    Config=config,
)

When the person uploading should not have an AWS identity

None of the above helps the colleague who just needs to put a signed contract somewhere. Three options exist, and they differ in what they record.

A presigned URL for a PUT lets one recipient upload one object without an AWS sign-in, using the permissions of whoever signed it. It is a bearer token: anyone holding it can use it until it expires, and it tells you nothing reliable about who actually uploaded. An AWS Transfer Family web app gives you an AWS-managed browser portal for browsing, uploading and downloading, with sign-in through IAM Identity Center and authorization through S3 Access Grants.

A workspace is the third option, and the one to compare on identity rather than features. BucketDesk connects to a customer-owned bucket through a scoped IAM role deployed by CloudFormation, so people sign in to BucketDesk instead of receiving AWS keys, and each action carries a name. Uploads are a Business-plan capability; Starter and Pro are read, preview and share. Check that against what your team actually needs to do before you assume a tier.

A short checklist before you automate an upload path

  • Set Content-Type, and Content-Disposition where it matters, at upload time rather than repairing objects later.
  • Use sync, not cp, for anything repeated, and dry-run every sync that can delete.
  • Add an AbortIncompleteMultipartUpload lifecycle rule to every bucket that receives large objects.
  • Set multipart_chunksize deliberately for objects past roughly 80 GiB, so the 10,000-part cap is never the thing that fails.
  • Decide who needs an AWS identity and who needs an account in something else. That choice, not the upload command, is what your audit log will reflect.
Try it in BucketDesk

Starter is free. Deploy a scoped role with CloudFormation, sign in, and browse, without handing anyone an access key.

Connect a bucket

Primary sources

Discussion

0 comments · open to guests · moderated
Comments appear after a quick review.

Liked this? Get the next article by email. No schedule, no filler, one click to leave.

Keep reading

All writing →