
September 09, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
Amazon S3 file uploads with the AWS SDK are the standard way to move user files, exports, and media off your web server and into durable object storage. Whether you run Laravel on EC2, a Node.js API, or a Python worker, the SDK wraps S3's REST API with retries, signing, and streaming so you do not hand-roll SigV4 requests. This guide walks through install, configuration, single and multipart uploads, presigned URLs, and the production mistakes I see on real client projects—including patterns from Laravel S3 file storage setup and document portals that rely on secure uploads.
What is the AWS SDK and how does it handle Amazon S3 file uploads?
The AWS SDK is Amazon's official client library for each language runtime. It signs HTTP requests, handles retries with exponential backoff, and exposes high-level methods like putObject and upload. You never send raw REST calls to s3.amazonaws.com unless you have a very specific reason.
S3 stores objects in buckets. Each object has a key (path), optional metadata, ACL or bucket-policy permissions, and optional server-side encryption. The SDK serialises your file bytes—or a stream—into that object model.
On production Laravel applications I maintain, the SDK sits between the app and S3 like this:
The SDK supports three credential sources in order of preference for servers:
- IAM instance profile on EC2 or ECS task role—no keys in
.env - Environment variables
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEY - Shared credentials file at
~/.aws/credentialsfor local dev
For local development against S3-compatible storage, see MinIO self-hosted S3 storage. The same SDK calls work when you set a custom endpoint.
How do you install and configure the AWS SDK for S3 uploads?
Pick the SDK package for your runtime. All share the same concepts: create a client, pass region and credentials, call an upload operation.
PHP (Laravel and standalone)
Laravel 12 and 13 ship with Flysystem S3 support, which uses aws/aws-sdk-php under the hood. For direct SDK usage or custom logic outside Flysystem:
composer require aws/aws-sdk-php:^3.0
# .env — never commit real keys
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
AWS_DEFAULT_REGION=ap-south-1
AWS_BUCKET=my-app-uploads <?php
use Aws\S3\S3Client;
$s3 = new S3Client([
'version' => 'latest',
'region' => env('AWS_DEFAULT_REGION', 'ap-south-1'),
'credentials' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
],
]); PHP 8.3 or higher is required for Laravel 13. PHP 8.2 works with Laravel 12. Store secrets in AWS Secrets Manager on production rather than plain environment files when compliance matters.
Node.js
npm install @aws-sdk/client-s3 @aws-sdk/lib-storage
# package.json should target Node.js 26 LTS or 24 LTS Python
pip install boto3
# boto3 picks up ~/.aws/credentials or env vars automatically Region choice matters. Nepal-based apps often use ap-south-1 (Mumbai) for lower latency and predictable data residency within AWS's published region list. Cross-check pricing against Cloudflare R2 vs AWS S3 cost breakdown if egress fees dominate your bill.
| SDK package | Runtime | Best for | Upload helper |
|---|---|---|---|
| aws/aws-sdk-php | PHP 8.2+ | Laravel, Symfony APIs | S3Client::putObject, TransferManager |
| @aws-sdk/client-s3 | Node.js 24/26 LTS | Express, serverless handlers | PutObjectCommand, @aws-sdk/lib-storage |
| boto3 | Python 3.10+ | Workers, data pipelines | client.upload_file, multipart config |
How do you upload a file to Amazon S3 using PutObject?
PutObject is the simplest upload API. Use it for files under roughly 100 MB. AWS allows up to 5 GB per PutObject, but loading a multi-gigabyte file into memory is a bad idea on a typical web server.
PHP example
$result = $s3->putObject([
'Bucket' => env('AWS_BUCKET'),
'Key' => 'uploads/' . $safeFilename,
'SourceFile' => $request->file('document')->getRealPath(),
'ContentType' => $request->file('document')->getMimeType(),
'ServerSideEncryption' => 'AES256',
'Metadata' => [
'uploaded-by' => (string) auth()->id(),
],
]);
$etag = $result['ETag'];
$url = $s3->getObjectUrl(env('AWS_BUCKET'), 'uploads/' . $safeFilename); Node.js example
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { readFileSync } from "fs";
const client = new S3Client({ region: "ap-south-1" });
await client.send(new PutObjectCommand({
Bucket: "my-app-uploads",
Key: `uploads/${safeName}`,
Body: readFileSync(localPath),
ContentType: "application/pdf",
ServerSideEncryption: "AES256",
})); Python example
import boto3
s3 = boto3.client("s3", region_name="ap-south-1")
s3.upload_file(
localPath,
"my-app-uploads",
f"uploads/{safe_name}",
ExtraArgs={
"ContentType": "application/pdf",
"ServerSideEncryption": "AES256",
},
) Always sanitise the object key. Strip path traversal, normalise Unicode, and generate your own filename rather than trusting the client's original name. Full rules live in our file upload security guide.
For Laravel-specific Flysystem configuration—disk drivers, visibility, and URL generation—read Laravel file uploads with S3, R2, and local storage. The SDK layer is the same; Laravel wraps it.
When should you use multipart upload instead of PutObject?
Multipart upload splits a large file into parts (minimum 5 MB each, except the last). Parts upload in parallel. A failed part retries without restarting the whole file. The SDK's high-level upload helpers switch to multipart automatically above a threshold.
PHP TransferManager (automatic multipart)
use Aws\S3\Transfer;
$transfer = new Transfer($s3, '/tmp/large-export.zip', 's3://my-app-uploads/exports/large-export.zip');
$transfer->transfer(); Node.js @aws-sdk/lib-storage
import { Upload } from "@aws-sdk/lib-storage";
const upload = new Upload({
client,
params: {
Bucket: "my-app-uploads",
Key: "exports/large-export.zip",
Body: createReadStream("/tmp/large-export.zip"),
},
queueSize: 4,
partSize: 10 * 1024 * 1024,
});
await upload.done(); A common production bug is orphaned multipart uploads after client disconnects. Set a bucket lifecycle rule to abort incomplete uploads after seven days. That saves storage cost on half-finished 2 GB video uploads.
For nightly database dumps and media archives, the same SDK patterns appear in automated off-site backups to S3. Treat backups as uploads with encryption and a predictable key prefix like backups/2026/09/09/.
How do you upload directly from the browser with presigned URLs?
Routing every file through your PHP-FPM or Node server wastes bandwidth and ties up workers. Presigned URLs let the client PUT or POST straight to S3 with a time-limited signature your backend generates.
PHP presigned PUT URL
use Aws\S3\S3Client;
$cmd = $s3->getCommand('PutObject', [
'Bucket' => env('AWS_BUCKET'),
'Key' => 'client-docs/' . $uuid . '.pdf',
'ContentType' => 'application/pdf',
]);
$request = $s3->createPresignedRequest($cmd, '+10 minutes');
$presignedUrl = (string) $request->getUri();
// Return JSON: { "url": "...", "key": "client-docs/..." } The browser sends PUT to that URL with the file body and matching Content-Type header. Your API records the pending upload when issuing the URL. After upload, verify with headObject or an S3 event notification before marking the document as available.
On a legal-tech client portal I built, presigned uploads kept PDF evidence off the app server. That reduced attack surface and matched how Mijar Law Associates client portal handles sensitive documents. Same pattern applies to any high-volume media workflow like Quick And Easy Nepalese Grocery product images.
How do you secure Amazon S3 file uploads and avoid production failures?
Upload code that works in staging fails in production for predictable reasons. Lock these down before launch.
IAM least privilege
Grant only the actions you need. A typical app user policy:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts"
],
"Resource": "arn:aws:s3:::my-app-uploads/uploads/*"
}]
} Do not attach s3:* on arn:aws:s3:::* to application credentials. Rotate keys if they ever leak. Prefer IAM roles over static keys on EC2—see hosting Laravel on AWS EC2 with RDS and S3.
Block public access and use bucket policies
Enable S3 Block Public Access at the account and bucket level. Serve files through CloudFront with signed URLs if users need download links. Never set public-read ACL on user uploads—that is an open invitation for malware hosting.
Encryption
Default to SSE-S3 (AES256) or SSE-KMS for audit trails. KMS adds per-request cost but fits regulated document storage. Details in AWS KMS envelope encryption explained.
CORS for browser uploads
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["PUT", "POST", "GET"],
"AllowedOrigins": ["https://yourdomain.com"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
] Restrict AllowedOrigins to your real domains. Wildcard * on a bucket that accepts presigned PUT is risky.
Error handling checklist
- Catch
S3Exception(PHP) orS3ServiceException(Node) and log the AWS error code—not just "upload failed" - Retry transient errors (
503 SlowDown, timeouts); the SDK retries by default but custom wrappers should not swallow exceptions - Validate MIME type with file content inspection, not only the client-supplied header
- Set max upload size in Nginx/Apache and in application validation
- After deploy, confirm PHP-FPM opcache and the correct IAM role are active—I've seen uploads fail because staging keys were still in
.env
Official references: the AWS SDK for PHP S3 multipart upload guide and the Amazon S3 upload objects documentation cover API limits and edge cases. For signature version details, see the AWS Signature Version 4 signing spec.
If uploads are one piece of a larger API surface, our API development service and SDK design for public APIs article cover versioning and idempotency patterns that pair well with S3 object keys.
Need to debug encoded payloads during integration? The Base64 encoder and decoder helps inspect small test files without writing throwaway scripts.
Ongoing upload failures after infrastructure changes often need server-level review—Linux system administration and support and maintenance cover the ops side when the SDK code itself is correct.
Key Takeaways
- Configure the AWS SDK with region, least-privilege IAM, and never commit access keys to git.
- Use PutObject for small files; switch to multipart or TransferManager/lib-storage for large streams.
- Presigned URLs offload bandwidth to S3—validate auth before signing and confirm upload server-side.
- Sanitise object keys, enforce Content-Type, and block public ACLs on user-generated content.
- Set lifecycle rules to abort stale multipart uploads and control storage cost.
- Pair SDK uploads with upload security validation and encryption defaults on every bucket.
People Also Ask
What is the maximum file size for a single PutObject upload?
Amazon S3 accepts up to 5 GB in one PutObject request. In practice, use multipart upload above 100 MB so your app streams from disk and survives network blips without holding the entire file in memory.
Can the AWS SDK upload to S3 without access keys?
Yes. On EC2, ECS, or Lambda, attach an IAM role. The SDK loads temporary credentials from the instance metadata service or container credentials automatically. No keys in environment variables are required.
How do I upload to S3 from Laravel without writing raw SDK code?
Configure the s3 disk in config/filesystems.php and call Storage::disk('s3')->put(). Laravel uses the AWS SDK for PHP internally. Custom presigned URL logic still uses the underlying S3 client when needed.
Is boto3 the same as the AWS SDK?
boto3 is the AWS SDK for Python. It wraps the same S3 APIs as aws-sdk-php and @aws-sdk/client-s3. Concepts—buckets, keys, multipart, presigned URLs—transfer directly across languages.
Ship reliable S3 uploads on your next project
Amazon S3 file uploads with the AWS SDK are straightforward once credentials, bucket policy, and upload method match your file sizes and traffic pattern. Start with PutObject and SSE encryption, add presigned URLs when browser uploads saturate your servers, and enforce multipart with lifecycle cleanup before you launch large-file features. If you want help wiring S3 into a Laravel app, document portal, or eCommerce catalog on AWS, contact us or browse custom software development and the project portfolio for examples of production file workflows. For broader cloud architecture context, read AWS vs DigitalOcean vs Hetzner for Laravel hosting and about the author.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

