Guides/Web Development
Web Development9 min read

How to Set Up AWS S3 for File Uploads in a Web App

S3 is the standard solution for file storage in web apps. This guide covers creating a bucket, setting up IAM permissions, generating presigned URLs for direct browser uploads, and serving files securely.

Create an S3 Bucket

In the AWS Console, go to S3 → Create bucket. Choose a globally unique name and your preferred region (same region as your server reduces latency and costs). Uncheck "Block all public access" only if you need public files (like user profile photos). For private files (documents, sensitive uploads), keep the block enabled and use presigned URLs to grant temporary access. Enable versioning for important files.

Create an IAM User with Minimal Permissions

Never use root AWS credentials in your app. Create an IAM user in IAM → Users → Create user. Attach a custom policy: {"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:PutObject","s3:GetObject","s3:DeleteObject"],"Resource":"arn:aws:s3:::your-bucket-name/*"}]}. Generate Access Key credentials. Store them in .env.local: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. This user can only access your specific bucket.

Generate Presigned URLs for Direct Browser Uploads

The recommended pattern: browser requests a presigned URL from your API, browser uploads directly to S3 (bypasses your server). In your API route: import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; const url = await getSignedUrl(s3Client, new PutObjectCommand({ Bucket: "mybucket", Key: `uploads/${uuid()}.jpg`, ContentType: "image/jpeg" }), { expiresIn: 300 }). The presigned URL is valid for 5 minutes.

Upload from the Browser

After getting the presigned URL from your API: const response = await fetch(presignedUrl, { method: "PUT", body: file, headers: { "Content-Type": file.type } }). On success (HTTP 200), save the S3 key (not the presigned URL — it expires) to your database. To generate a download URL later: getSignedUrl(s3Client, new GetObjectCommand({ Bucket, Key }), { expiresIn: 3600 }). This gives a 1-hour download link.

Set Up CloudFront for Fast Global Delivery

For publicly accessible files, put CloudFront (CDN) in front of S3. In AWS Console → CloudFront → Create Distribution, set the origin to your S3 bucket. Use an Origin Access Control to block direct S3 access (files only served through CloudFront). Files are cached at edge locations worldwide — a user in Tokyo gets your US-hosted files from a Tokyo server. CloudFront also handles SSL automatically.

Need Help?

Want this done for you?

Our engineering team handles implementations like this every week. Get a free scoping call — we will tell you exactly what it takes and what it costs.

Book a free call

© 2026 NexWorldTech — Built for Global Dominance.