The question usually arrives the same way: a media library creeping past 2 TB, a df -h showing the NVMe volume at 94% and a hosting panel where the next disk tier costs more than the whole VPS. At that point S3 starts looking obvious. Object storage is cheap per TB, grows without repartitioning anything and Nextcloud supports it. What's less obvious, and what this article is really about, is that "Nextcloud supports S3" describes two completely different features, and picking the wrong one on an existing instance can cost you access to every file you have.
The two S3 modes people confuse
Mode one is primary object storage. Nextcloud stops using the local data directory for file content and writes everything to the bucket instead. Mode two is the External Storage app, which mounts a bucket as one folder inside an otherwise normal instance. Both get called "Nextcloud S3" in forum threads, which is how people end up applying primary-storage config to an instance that needed a mount, or vice versa. The two modes differ in performance, in what the objects look like inside the bucket and above all in when you're allowed to make the choice. Primary storage is a day-one decision. The mount you can add any afternoon.
Primary object storage: how it works and its hard limit
With an objectstore block in config.php, file content lives in the bucket as opaque objects named by internal file ID, while all metadata (filenames, the directory tree, shares, versions) stays in the Nextcloud database. The bucket contains no folders and nothing human-readable. Point rclone or another S3 browser at it and you'll see thousands of urn:oid objects with no way to tell a tax document from a cat photo. Files are effectively unreadable outside Nextcloud, and losing the database means losing the map to everything.
In exchange you get the better-performing of the two modes. Folder listings never touch the bucket at all, they're pure database reads, and file transfers stream through with less overhead than the External Storage layer adds. The primary storage docs are upfront about the trade and carry a warning I'll quote outright because it's the most important sentence on this page: configuring a primary object store on an existing Nextcloud instance will make all existing files on the instance inaccessible. Nextcloud won't convert your local files. It just starts looking in the bucket, finds nothing and shows you an instance where your data appears to be gone. This is a decision for install day, before the first real file is uploaded.
Config walkthrough: S3 as primary storage
On a fresh install, this goes into config/config.php before you create the admin account (my manual walkthrough in installing Nextcloud on Ubuntu shows where config.php first appears in that flow):
'objectstore' => [
'class' => '\\OC\\Files\\ObjectStore\\S3',
'arguments' => [
'bucket' => 'nextcloud-data',
'autocreate' => true,
'hostname' => 's3.example.com',
'key' => 'ACCESS_KEY',
'secret' => 'SECRET_KEY',
'port' => 443,
'use_ssl' => true,
'use_path_style' => true,
],
],
use_path_style is the parameter that trips most first attempts. Amazon itself uses virtual-hosted bucket addressing, but most S3-compatible services (MinIO on your own hardware, plus many independent providers) want path-style requests, so the value depends on who's hosting the bucket. autocreate saves you a trip to the provider console by creating the bucket on first use, assuming the credentials are allowed to. Test with a small upload, then check the bucket: if you see an urn:oid object appear, the wiring works.
A dry note on credentials: this key pair can delete your entire cloud. Scope it to this one bucket and keep it out of any repo where you version config files.
Config walkthrough: the External Storage app
The mount route needs no config.php surgery. Enable the External storage support app from the app bundle, then go to Settings, Administration, External storage and add a mount with the Amazon S3 backend. The form wants the same facts as the config block above: bucket name, hostname and port, region for AWS proper, the SSL and path-style checkboxes and the access key pair. You pick the folder name users will see and which groups get the mount. A green dot appears next to the row when Nextcloud can reach the bucket, a red one with no further explanation when it can't (the log knows more, it usually turns out to be path style or region). The Amazon S3 external storage docs cover the full field list, including the session token and legacy auth options I've never needed.
The behavior difference from primary storage is the point. Objects keep their real names and folder paths in the bucket, so the same data stays usable by other tools: rclone syncs it, a static site can serve from it, another app can share the bucket. Nextcloud becomes one consumer of ordinary objects instead of the sole owner of an opaque blob pile. The cost is speed, every folder listing is S3 API calls rather than a database read, and deep trees with thousands of files make that painfully visible.
What S3 actually feels like day to day
Community reports on Nextcloud-over-S3 performance disagree wildly, and having run the setup experimentally I think I know why: people are comparing different providers, different regions, different clients and different failure budgets. A bucket 2 ms from your VPS in the same facility behaves nothing like one across an ocean adding 120 ms to every request. Request pricing shapes the experience too, some providers bill per GET, and a preview-hungry photo app makes a spectacular number of GETs.
Previews deserve their own warning. Thumbnails get generated from full-size originals, so a first browse through a photo folder pulls each original out of the bucket, renders previews and does it again for the next uncached size. On a metered bucket that's a bill; on a distant one it's a slideshow. Pre-generating previews (covered in my Nextcloud performance tuning guide) goes from nice-to-have to load-bearing on S3, and folder listings on external mounts stay slower than local no matter what you tune.
My honest expectation-setting: with a nearby bucket and pre-generated previews, primary object storage feels close to local for everyday sync and share use. External mounts feel like what they are, a remote filesystem, fine for archives and media libraries, annoying for folders you rummage through daily.
When I'd skip S3 and buy disk instead
If the only problem is that 2 TB doesn't fit on NVMe, S3 is one answer and a bigger disk is a simpler one. Local storage keeps the whole system in one place: one thing to monitor, one thing to back up, no request fees, no second provider in the failure chain and none of the migration questions below. For a personal or small-team instance I'd sooner move the whole thing to a storage VPS with a few TB of HDD behind it than bolt object storage onto a small instance, and my own 400 GB of photos and client files still sits happily on plain local disk with restic shipping backups offsite. S3 earns its complexity when growth is open-ended, when you want storage decoupled from the server's lifespan or when the bucket already exists for other reasons.
Migration reality: there's no in-place switch
The question everyone asks eventually: my instance is on local disk, how do I switch to primary S3? The unpopular answer is that no supported in-place migration exists. The config change makes existing files inaccessible, per the warning above, and while community scripts float around that try to rewrite the database and push files into the bucket in Nextcloud's internal format, they operate unsupported on the exact two things you can't afford to corrupt. I wouldn't run them against data I care about.
The honest paths are two. Either keep local storage as primary and add the bucket as an external mount for the bulky folders, which solves the disk problem without touching the foundation. Or build a fresh instance configured for primary S3 from day one and move the data through the front door: sync clients re-upload, or rclone against WebDAV, then shares and settings get recreated by hand. Tedious, yes. Also boring and safe, and boring is what you want near 2 TB of family history.
Backups when the files live in a bucket
Bucket versioning looks like a backup and isn't one. It protects individual objects against overwrite and deletion, but it has no idea what a consistent Nextcloud looks like: restoring "the bucket as of Tuesday" object by object while the database says Thursday gets you an instance that disagrees with itself. And with primary S3 the database stops being merely important and becomes the only copy of your file tree, every filename in your cloud exists solely as rows in MariaDB. So the backup discipline tightens: scheduled database dumps with tested restores, plus a copy of config.php and the objectstore credentials stored somewhere safe. I've laid out the full routine, dump commands and restore drills included, in the Nextcloud backup and restore playbook, and it applies double here.
So, the decision in two lines. Installing fresh and expecting open-ended growth: put the objectstore block in before the first login and enjoy the better mode while it's still available to you. Running an existing instance: mount, don't migrate.

