Almost every business application stores files: invoices, product photos, KYC documents, signed contracts, exports, and user uploads. Many start by saving them to a folder on the application server. That works until the second server is added, the disk fills up, or the server has to be rebuilt and nobody is sure the uploads were backed up. Object storage such as Amazon S3 solves these problems, as long as it is set up with security and cost in mind from the start.
Why Object Storage Instead of the Server Disk
| Aspect | Files on the server disk | Object storage such as S3 |
|---|---|---|
| Scaling to several servers | Every server needs the same files, which means shared folders or syncing | All servers read and write the same bucket over an API |
| Capacity | Limited by the disk, which has to be resized as data grows | Effectively unlimited, and you pay for what you store |
| Durability | Depends on your own backups and RAID | S3 is designed for 99.999999999 percent durability by storing copies across several facilities |
| Delivery to users | Every download uses the application server's bandwidth and workers | Users download directly from storage or a CDN |
| Rebuilding a server | Uploads must be restored before the application works again | The server holds no files, so it can be replaced at any time |
Object storage is not a normal file system. Files are stored as objects with a key, such as invoices/2026/10/abc123.pdf, and accessed through an HTTP API. Renaming a folder means copying and deleting every object inside it, and there is no file locking. Design the application to write a file once and refer to it by key, and object storage fits very well.
Keep Buckets Private
Most S3 data leaks come from a bucket that was made public for convenience. New buckets on AWS block public access by default, and that setting should stay on for anything containing customer data. Instead of public links, the application generates presigned URLs: temporary links signed with the application's credentials, valid for a few minutes, and limited to one object. A user who is allowed to see an invoice gets a link that works for five minutes, and anyone who finds that link a week later gets an error.
- Give the application an IAM role with access to its own bucket only, and to the specific actions it needs.
- Never put AWS access keys in frontend code or a mobile app. The browser receives presigned URLs from your backend instead.
- Leave default encryption on. New objects in S3 are encrypted at rest automatically, and you can use KMS keys when compliance requires key control.
- Separate buckets per environment, so a test script can never delete production files.
- Turn on server access logging or CloudTrail data events for buckets with sensitive documents, so you can see who accessed what.
Upload Directly from the Browser
Sending a 200 MB video through the application server ties up a worker for the whole upload and uses the server's memory and bandwidth. A better pattern is to let the browser upload straight to the bucket with a presigned URL. The application only checks permissions, issues the URL, and records the result.
- 1The browser asks the backend for permission to upload, sending the file name, type, and size.
- 2The backend checks the user's permission and the file limits, generates a unique key such as a UUID, and returns a presigned URL or a presigned POST with size and content-type conditions.
- 3The browser uploads the file directly to S3. Large files use multipart upload, so a broken connection only resends one part.
- 4The browser tells the backend the upload is complete, and the backend verifies the object exists before saving its key in the database.
- 5Follow-up work such as generating thumbnails, extracting text, or scanning for malware runs in a background job.
Browser uploads need a CORS rule on the bucket that allows your application's domain. Store the object key in the database, never the full URL, so you can change the CDN domain or move to another provider later without rewriting every record. Avoid using the user's original file name as the key, because it can contain characters that cause problems and may reveal personal information.
Serve Public Files Through a CDN
Product images, banners, and other files meant for everyone can be served through CloudFront in front of a private bucket. Origin Access Control lets only CloudFront read the bucket, so the bucket itself stays private. Users get faster downloads from a nearby edge location, data transfer through CloudFront is usually cheaper than serving straight from S3, and you can add cache headers so browsers do not download the same image twice.
Choose Storage Classes and Lifecycle Rules
| Storage class | Fits | Watch out for |
|---|---|---|
| S3 Standard | Files that are accessed often, such as active uploads and images | The highest price per GB of the common classes |
| S3 Standard-IA | Files kept for a long time but rarely opened, such as old invoices | A retrieval fee per GB and a 30-day minimum storage charge |
| S3 Intelligent-Tiering | Data with unpredictable access patterns | A small monitoring fee per object, which adds up for millions of tiny files |
| S3 Glacier classes | Archives and backups that are rarely restored | Retrieval can take minutes to hours depending on the class, and minimum storage periods apply |
Lifecycle rules move objects automatically, for example to Standard-IA after 90 days and to Glacier after a year, and delete temporary files such as exports after a week. Add a rule to abort incomplete multipart uploads too, because unfinished uploads are invisible in the console but still billed.
Protect Against Deletion and Plan Backups
- Turn on versioning for buckets with important documents, so an overwritten or deleted file can be restored.
- Add a lifecycle rule for old versions, otherwise versioning quietly doubles your storage bill.
- Replicate critical buckets to another region or another AWS account, so one compromised account cannot delete everything.
- Use Object Lock for backups that must not be changed or deleted for a set period, which also protects against ransomware.
- Test a restore regularly. A backup that has never been restored is only an assumption.
What Drives the Cost
An S3 bill has three main parts: storage per GB per month, requests, and data transfer out to the internet. Storage is usually the smallest surprise. Data transfer is the one to watch for applications that serve many downloads or videos, and a CDN or a provider without egress fees can change the numbers a lot. Requests matter for workloads with millions of small files, where a PUT costs more than a GET. Check prices for the region you use, because they differ between regions, including the Jakarta region.
S3-Compatible Alternatives
The S3 API has become a common standard, so most libraries work with other providers by changing the endpoint and credentials. Cloudflare R2 charges no egress fees, which suits download-heavy applications. Google Cloud Storage, DigitalOcean Spaces, and Wasabi are other common choices, and several Indonesian cloud providers offer S3-compatible object storage for data that must stay in the country. Self-hosted options such as MinIO exist for on-premise needs, but check the current license and maintenance status of the community edition before depending on it, and remember you then own durability and backups yourself.
Using It from Your Framework
- Laravel: the Storage facade with the s3 disk, which also works for S3-compatible providers by setting the endpoint. temporaryUrl generates presigned download links.
- Django: the django-storages package with the S3 backend, so FileField and ImageField save to the bucket.
- Node.js: the AWS SDK v3 client for S3 together with the request presigner package for upload and download URLs.
- Go: the AWS SDK for Go v2 with its S3 client and presign client.
Before going live, try opening one of your file URLs in a private browser window without logging in. If a customer document opens, the bucket or CDN is more public than you intended.
Key takeaways
- Move user uploads and documents from the server disk to object storage, so servers can scale and be replaced freely.
- Keep buckets private, give the application a narrow IAM role, and share files through short-lived presigned URLs.
- Let the browser upload directly to the bucket, and store the object key in the database instead of the full URL.
- Use CloudFront for public files, lifecycle rules for old data, and versioning or replication for important documents.
- Watch data transfer costs, and consider S3-compatible providers when egress or data location matters.


