The real Nextcloud limitations on a VPS
What Nextcloud is genuinely bad at on a VPS: many small files, the sync client on large trees, unconfigured cron, and the memory floor for office.
What Nextcloud is bad at, in the order you meet it
Most Nextcloud limitations on a VPS come from file count and request count rather than from disk size. A 200 GB library of video files behaves fine on a small server. The same 200 GB spread across 400,000 small files makes every folder listing slow and turns the database into the busiest process on the box. The order below is the order a self-hoster meets these limits: the PHP request model, then the sync client, then the database defaults, then background jobs, then memory.
Some of what people call "Nextcloud is slow" is a default that nobody changed. The rest is the design, and tuning will not remove it. Each section says which one it is.
Why PHP decides your concurrency ceiling
Nextcloud is written in PHP, and a PHP application is not a long-running server. Each request is handled by one worker process from a PHP-FPM (FastCGI process manager) pool, and that worker owns the request from start to finish. So the number of requests the server can handle at the same moment is pm.max_children, and each of those workers holds its own memory.
Two things follow from that. A slow request holds a worker for its whole life, so one user assembling a 4 GB upload occupies a slot nobody else can use. And when every worker is busy, new requests wait in the socket backlog. The pool says so in its log:
WARNING: [pool www] server reached pm.max_children setting (5), consider raising itThe reader usually meets the symptom first, as a 504 from the web server in front:
upstream timed out (110: Connection timed out) while reading response header from upstreamA stock distribution pool often ships pm.max_children = 5. That is a reasonable guess for a shared box and far too low for a Nextcloud instance with a dozen sync clients attached. Size it from memory you actually have: measure the resident size of a busy worker, leave room for the database and the cache, then divide.
pm = dynamic
pm.max_children = 12
pm.max_requests = 500
php_value[memory_limit] = 512Mmemory_limit is a ceiling per request, not an allocation. Most requests use a small fraction of it. Preview generation and large upload assembly are the ones that reach for the rest, which is why the limit and the child count have to be chosen together.
There is also no shared application memory between requests. Every request boots the framework again: config, enabled apps, routes, event listeners. OPcache keeps the compiled bytecode so this is execution rather than parsing, and APCu (alternative PHP cache) holds a small local key-value store in shared memory. That is why the admin overview line "No memory cache has been configured" matters. Without it, lookups that could be answered from memory go to the database on every single request. That part is configuration. The one-process-per-request model underneath it is architectural.
Why many small files hurt long before the disk does
Nextcloud keeps metadata for every file and folder in one database table, oc_filecache: path, path hash, size, modification time, etag and storage id. A folder listing is a query against that table, not a readdir on disk. A write updates the file's row and then propagates the new size and etag up through every parent folder to the root. One upload is one HTTP request, several SQL statements, and one more update per level of depth.
Transfer is per file as well. The sync protocol is WebDAV (web distributed authoring and versioning), and WebDAV moves one file per request. 400,000 files means 400,000 requests, each carrying authentication, framework bootstrap, permission checks and database writes. At 25 ms of server time each, the overhead alone comes to nearly three hours before a single byte of payload is counted. Treat that as arithmetic, not as a benchmark. The point it makes is the one that matters: cost tracks the number of files, not their size.
Faster storage helps with part of this. Random small reads and writes are exactly where NVMe and SATA SSD VPS storage differ most, so NVMe is worth having under a large instance. It leaves the per-request cost untouched, because that cost is PHP and SQL rather than the disk.
One consequence catches everybody once. Files copied onto the server outside Nextcloud do not exist as far as Nextcloud is concerned, because nothing ever wrote the row. They appear only after occ files:scan walks the tree and writes the missing rows, and on a large tree that scan is long. If you are unsure where those files even sit under a container install, the data directory layout for Nextcloud in Docker is a separate walk-through. This one is architectural: the database is the file listing.
Why the desktop client slows down on a large tree
The sync client is smarter than its reputation. Because a write propagates a new etag up to the root folder, the client can ask one cheap question to learn whether anything at all has changed, then descend only into folders whose etag differs. Idle polling is therefore not expensive by itself.
The costs that remain are real. The first sync walks every folder, one PROPFIND request per folder, and downloads every file. A rename near the top of the tree changes the etag of everything beneath it, so the client re-examines that whole subtree. The client also keeps its own local database and stats the local files, so a slow laptop disk or an aggressive antivirus scanner adds delay that has nothing to do with your server at all.
Without the Client Push app, notify_push, every connected client polls on a timer. Thirty clients produce a steady background request rate against the same worker pool that serves the web interface. notify_push is a small separate service that holds a websocket per client and tells them when something changed, which removes most of that idle traffic. On a busy instance it is one of the highest-value additions you can make.
There is no merge. Two people editing the same file while offline produce a conflict copy, named like budget (conflicted copy 2026-08-22 141530).ods, and a human resolves it by hand. Editing through an office service avoids this, because then only one copy on the server is ever authoritative. Virtual files, where a file shows as a placeholder until it is opened, is stable on Windows and macOS and has been slower to settle on Linux, so check your client version before you plan a workflow around it. The polling is configuration. One request per file is architectural.
What a default install leaves undone
A fresh Nextcloud works. Four things are still missing on a typical manual install, and all four are visible in Settings, Administration, Overview.
- No memory cache. The overview says "No memory cache has been configured". APCu costs almost nothing and removes a large number of repeated database lookups.
- File locking in the database. Nextcloud locks a file while it is being written. Without Redis that lock lives in a database table, which adds write load and leaves stale locks behind after a request dies. Users then see errors saying the file is locked, and only a manual cleanup clears them.
- Missing database indexes. The overview says "The database is missing some indexes" and names
occ db:add-missing-indices. On a filecache with millions of rows, those indexes are the difference between a fast listing and a slow one. - SQLite. A manual install can end up on SQLite, which the documentation supports for a single user and for testing. Writes serialize, so a second active user is already one too many.
'memcache.local' => '\\OC\\Memcache\\APCu',
'memcache.locking' => '\\OC\\Memcache\\Redis',
'redis' => [
'host' => 'localhost',
'port' => 6379,
],None of this is exotic, and all of it is configuration. If you would rather start from an install where these decisions are already made, the Docker install with TLS and backups brings up the cache and the cron container as part of the stack.
Why background jobs stall when cron is not configured
Nextcloud pushes real work into background jobs: preview generation, expiring old file versions, emptying the trash on schedule, sending notification mail, cleaning up shares. There are three supported ways to run them, and the default is the weakest.
AJAX mode runs one job when a browser loads a page. On a personal instance that nobody visits for a week, nothing runs for a week. Versions and trash never expire, so the disk keeps filling with data the user believes they deleted. Previews are never pre-generated, so the first person to open a photo folder waits while their own request generates them. Eventually the overview reports that the last background job ran hours ago and that "Something seems wrong", which is the only warning you get.
Webcron and system cron both fix it. System cron runs cron.php every five minutes as the web server user, and a container install runs the same thing from a dedicated cron container. Five minutes is the interval the documentation specifies, and job intervals are tuned around it.
The remaining cost is that jobs are serialized. A long job, a preview batch or a file scan, delays everything queued behind it, and on a single-core VPS that run competes with live requests. On a busy host, CPU steal time from noisy neighbours stretches those runs past what your own numbers predict. Configuration, and the single highest-value fix on most neglected instances.
The memory floor once you add office, Talk or previews
Plain Nextcloud is modest: a PHP pool, a database, Redis, a web server. A small instance is comfortable in 2 GB. Every feature people actually want is a separate service with its own floor.
Previews. By default a preview is generated when it is first requested, so the user waits inside the request that needs it. The Preview Generator app moves that work to cron, which trades the wait for CPU during cron runs. Either way, each source image produces several preview files at different sizes, so a photo library multiplies your file count and feeds straight back into the small-file problem above. Large source images are also where the memory limit gets hit, as Allowed memory size of 536870912 bytes exhausted in the PHP error log. Bound it with preview_max_x and preview_max_y rather than raising the limit forever.
Office. Collabora Online and OnlyOffice Docs each run as their own service, usually a separate container, with their own runtime and their own memory. Document sessions cost CPU and RAM there, not in PHP. Budget a gigabyte or two before it feels responsive, and read the vendor's current requirements as of August 2026 before you size the box. The two differ in more than resource use, and OnlyOffice against Collabora for self-hosting covers that split.
Talk. Calls between a few people go peer to peer over WebRTC, and the server only passes signalling messages. Past roughly four participants, Nextcloud's own guidance points at the High Performance Backend, which is a separate signalling server. Many networks also need a TURN (traversal using relays around NAT) server such as coturn, and relayed media flows through your VPS, so bandwidth becomes a real line item.
Full-text search. An Elasticsearch instance wants a gigabyte or more of heap for itself, before it indexes anything.
How much VPS does Nextcloud need?
Sizing by user count alone is misleading, because behaviour dominates. Five developers syncing a 300,000-file source tree are heavier than thirty people who open documents in a browser. Use these as starting points to test against your own file counts, not as measurements.
- 1 vCPU and 1 to 2 GB: one or two users, files and calendar only, previews bounded hard, no office service. Upgrades and a full file scan will be the slowest things that ever happen on the box.
- 2 vCPU and 4 GB: roughly 5 to 15 regular users, with APCu, Redis locking, system cron and previews generated by cron.
- 4 vCPU and 8 GB: the same user range plus an office service, or 15 to 40 light users without one. Office sessions are what drive the memory here.
- 4 or more vCPU and 16 GB: Talk with the High Performance Backend, full-text search, or bulk preview generation over a photo library.
Disk is a separate calculation. Previews, file versions and trash sit on top of your actual data, and they shrink only when background jobs really run. Whether the whole thing costs less than a subscription depends on those numbers, and self-hosted storage against Dropbox pricing works that arithmetic through.
Which Nextcloud limitations are configuration, and which are architectural
Fixable in an afternoon: the memory cache and Redis locking, the missing database indexes, the background job mode, the FPM pool size and PHP memory limit, OPcache sizing, notify_push, and preview bounds. An instance with all of that done is a different piece of software from a default install, on the same hardware.
Permanent, whatever you tune:
- One process per request. Concurrency is bounded by RAM, and one slow request holds one worker for its whole duration.
- One database row per file, with size and etag propagated up every parent. Cost tracks file count.
- One request per file on the wire. Syncing many small files is bound by request overhead rather than by bandwidth.
- A groupware platform with a file application inside it. The baseline includes machinery that a pure sync tool never loads.
- Serialized upgrades. Major versions are applied one at a time, in maintenance mode, with app compatibility to clear at each step.
None of these are bugs. They are the price of share links, external collaborators, mobile clients, and one account system across all of it. Paying for the enterprise edition buys support and some extra apps, and it does not change the request model described above, which is one of the things what the free Nextcloud edition actually includes sets out.
Who should not run Nextcloud on a VPS
- You want fast sync of a large tree between a few machines and nothing else. A peer-to-peer tool keeps no per-file rows in a web application's database, so the small-file cost looks completely different: Syncthing compared with Nextcloud for plain sync.
- You want sharing and a web interface, but your data is millions of small files. Seafile stores content in a block store instead of one file per file, which changes that arithmetic. The tradeoff is that your data stops being ordinary files on disk: Seafile against Nextcloud.
- Your real requirement is a shared drive for four people. Start lighter and add later: the self-hosted alternatives to Nextcloud.
- You have 1 vCPU and 1 GB and you want browser-based office editing. That combination does not fit, and no amount of tuning makes it fit.
Run Nextcloud when you want the groupware: shares with people outside your organisation, calendar and contacts on every phone, editing in the browser, and one login across all of it. The failure mode is a slow slide rather than a wall. File count grows, the install still has no cache and no cron, and one day a folder takes eight seconds to open. Every part of that is measurable, and most of it is fixable before it starts.
FAQ
How many users can one Nextcloud VPS handle?
Behaviour decides this more than headcount. A tuned 2 vCPU and 4 GB server is comfortable for roughly 5 to 15 regular users doing files, calendar and contacts. Adding an office service usually means 8 GB. The limit you meet first is normally the PHP worker count against available RAM, or the database once oc_filecache is large, so test with your own file counts before committing to a plan size.
Why is Nextcloud slow with many small files?
Because every file is a database row and a separate WebDAV request. A folder listing is an SQL query, a write updates the file's row and then every parent folder's size and etag, and transfer moves one file per HTTP request with authentication and framework bootstrap each time. Cost therefore tracks file count rather than total size. Faster storage reduces the disk part of it and leaves the per-request part in place.
Do I need Redis for Nextcloud?
For a single user, no. For anything multi-user, yes. Without it, transactional file locking runs through a database table, which adds write load and leaves stale locks after a request dies, and users see errors telling them the file is locked. The usual arrangement is APCu for the local cache and Redis for locking, both set in config.php.
What happens if I never configure Nextcloud's background jobs?
The default AJAX mode runs one job per page load, so nothing runs while nobody is logged in. Previews are never pre-generated, file versions and trash never expire so the disk keeps growing, and notification mail stops going out. The admin overview eventually reports that the last background job ran hours ago and that something seems wrong. Switching to system cron every five minutes is the fix, and a container install does it from a dedicated cron container.