Object Storage for VPS Developers
Hey there. Long time no see. I am Abhay and I have been working as a software engineer from last four years and until now, every file my apps stored went to one place: a…
Hey there. Long time no see. I am Abhay and I have been working as a software engineer from last four years and until now, every file my apps stored went to one place: a folder on the VPS disk.
My next project stores user images and PDFs on Sakura Cloud Object Storage instead, and I assumed it would be the same thing on someone else’s disk.
It isn’t.

Object storage is a different model, a few of its rules change how you design uploads and downloads. This post covers the mental model, how to serve private files and how to handle uploads safely.
I will be using Sakura Internet for Object storage (cause this is the preferred provider in my workplace). But as Sakura’s object storage speaks the Amazon S3 API, so almost everything here applies to S3 and other S3-compatible services too.
Big Changes compared to Disk
Object Storage has three nouns. A bucket is a named container. An object is a file’s bytes plus some metadata. A key is the object’s name inside the bucket.
First big change: there are no folders. invoices/2026/inv-01.pdf is a single key, and slashes are just characters in it. Consoles and tools often display them as folders for simplicity, but remember nothing is nested.
Second big change: you don’t open files, you send HTTP request. PUT uploads an object, GET downloads it, DELETE removes it, and HEAD returns its metadata. Each request is signed with an access key, so any machine holding the key can reach the bucket, not only your server.
The third: objects are written whole. You can’t append to one or change bytes in the middle. To modify an object, you need to upload a full replacement.

What it’s good (and bad) at
A quick test makes the limits clear. Suppose your app writes one line to a log file for every upload. On a disk, that’s a cheap append. In object storage, each new line means downloading the whole log, adding the line, and uploading it again.
It gets worse with two writers trying to write at the same time:
REQUEST A: GET log.txt (100 lines)
REQUEST B: GET log.txt (100 lines)
REQUEST A: PUT log.txt (101 lines)
REQUEST B: PUT log.txt (101 lines)
This is a lost update: B’s upload replaces A’s without any error.
So object storage suits files that are written once and read many times: images, PDFs, videos, backups, exports. Data that changes a little at a time, like logs, counters and records, belongs in a database. If you must keep events in a bucket, write each one as its own object.
My project’s files are images and PDFs uploaded once and then only viewed, which is the ideal case.
Serving Private Files
Once a file is in the bucket, how does a user see it? There are three common patterns:
- Public bucket: Every object has a permanent URL anyone can open. Fine for public assets, wrong for private assets (like invoices etc).
- Proxy through your app: Your server fetches the object with its keys and passes the bytes on to the user.
- Presigned URL: Your server checks permission , then generates a URL that works for a few minutes. The browser downloads straight from Sakura.
A presigned URL is a normal object URL with a signature in the query string. Your server computes it locally with its secret key, without calling sakura. Sakura checks the signature and the expirey on each request.
I was confused here at first. I assumed a presigned URL forces a doenload, while a proxy lets the file render inside the app. In fact, display vs download is controlled by the Content-Disposition header ( inline vs attachment ), not by who sends the bytes. A presigned URL works fine in an <img> or <iframe>. And with either pattern, anyone who can view a file can save a copy.
The real difference shows when a link leaks:

For most apps, short-lived presigned URLs are the right default. Use a proxy when every single view must be checked, logged or watermarked.
Uploads: presigned PUT + verify
Uploads have two contraints. Your secret access key must never reach the browser, since anyone holding it can read and delete every file. And ideally, uploads shouldn’t pass through your VPS at all.
The answer is the same trick in reverse: a presigned URL for PUT

The signature fixes the HTTP method, the exact key and the expiry time, so the URL allows one upload to one path, briefly. The server, not the user, chooses the key. A random UUID stops users from overwriting each other’s files or guessing URLs.
One catch: a presigned PUT can’t limit file size. On AWS you’d use a POST upload with a size policy, but Sakura’s API list doesn’t include it. So the server verifies every upload when the browser reports it’s done:
- Is this key in this user’s pending-upload row? Otherwise a user could claim someone else’s file.
- Does it exist, and is the size OK? A HEAD request returns Content-Length without downloading the file.
- Is it really a PDF or Image? Content-Type is whatever the browser claimed. Fetch the first few bytes with a range GET and check the signature: PDFs start with %PDF , PNGs with /x89PNG .
If any of the above checks fails, delete the object and mark the row as failed.
Notes specific to Sakura
Sakura Cloud Object Storage offers two APIs: its own, and an Amazon S3-compatible one. The S3-compatible API covers everything above. Here’s what its manual lists as of September 2026:

Source: Sakura Cloud manual, Object Storage API.
Practical Notes:
First, a browser talking to Sakura directly needs CORS permission for your site’s domain. The API supports buckets CORS rules, but Sakura’s manual recommends its Web Accelerator for CORS when delivering files, so test your upload setup early. Second, feature support changes, so check the manual before you build anything.
Takeaways:
Moving from a disk to object storage comes down to three rules:
- Write files whole, once. Keep data that changes in a database.
- Serve private files with short-lived presigned URLs.
- Let browsers upload directly with presigned PUT URLs, then verify every file on the server.