Four changes take a default Nextcloud install from sluggish to acceptable: Redis for file locking, APCu plus a tuned OPcache, a real database instead of SQLite, and pregenerated previews. Together they cut web page loads from roughly 2 seconds to 400–500ms on an N100-class box. Set expectations now: Nextcloud is a large PHP application and will never feel like a static site or native Google Drive. Tuned, it feels fine; the gap between default and tuned is enormous, the gap between tuned and heroic tuning is small.
Caching: the single biggest Nextcloud performance win
Out of the box, Nextcloud does file locking in the database — every file operation writes lock rows, and under sync load that's a queue of contention. Move locking and local cache to memory in config.php:
'memcache.local' => '\OC\Memcache\APCu',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' => [
'host' => 'redis',
'port' => 6379,
],
Redis is a 15MB container sitting next to Nextcloud in the same compose stack; APCu ships in the official image. This one change is the difference between a sync client hammering the database and it not. If you take nothing else from this article, take this section.
PHP settings: OPcache and enough memory
The official image sets sane OPcache defaults, but two values are worth forcing in your PHP config:
opcache.interned_strings_buffer=16
memory_limit=512M
Nextcloud's own admin warnings will nag about the interned strings buffer; believe them. If you run PHP-FPM yourself rather than the Apache image, size pm.max_children as available RAM for PHP divided by ~60MB per worker — a 4GB allocation supports around 60 workers, which is far more concurrency than a household generates. Undersized worker pools look exactly like "Nextcloud is slow" while every graph shows an idle CPU.
Database and cron: Postgres, and never AJAX
SQLite is for trying Nextcloud, not running it — a single sync client can lock it. Postgres 16 is the well-trodden path and needs no tuning at household scale beyond existing. While you're there, run the two occ commands that fix the most common self-inflicted slowness:
docker exec -u www-data nextcloud php occ db:add-missing-indices
docker exec -u www-data nextcloud php occ maintenance:repair --include-expensive
Then set background jobs to real cron (Settings → Administration → Basic settings → "Cron"), with the host's crontab invoking cron.php every 5 minutes. The default AJAX mode runs maintenance only when someone loads a page, which means jobs pile up and then execute during your page load — exactly when you don't want them.
Previews: pregenerate or suffer
Thumbnail generation is Nextcloud's hidden CPU sink. The first time you open a folder of RAW photos, it generates every preview on demand while you watch a spinner. Install the Preview Generator app and shift that work to nightly cron:
docker exec -u www-data nextcloud php occ preview:generate-all # once, takes hours
# then nightly:
docker exec -u www-data nextcloud php occ preview:pre-generate
Cap sizes in config.php with 'preview_max_x' => 2048, 'preview_max_y' => 2048 — the default generates absurdly large previews that bloat storage by gigabytes on photo-heavy libraries. Expect the initial generate-all on a 100GB photo folder to run most of a day; it's a one-time cost.
The push service most people skip
Desktop and mobile clients discover changes by polling every 30 seconds, which is both laggy and load-generating with several devices. The Client Push app (notify_push) replaces polling with a persistent websocket — changes propagate in about a second and idle load drops. It needs one extra reverse-proxy route to the push daemon, documented in the Nextcloud admin manual, and it's the last piece that makes multi-device sync feel current rather than eventual.
Measure before and after, or you're guessing
Two numbers make tuning honest. First, time a cold API request from another machine: curl -o /dev/null -w '%{time_total}\n' -u user:app-password https://cloud.example.com/ocs/v2.php/cloud/capabilities — run it before any changes and after each one, and keep the log. Second, watch the admin overview page (Settings → Administration → Overview) until every warning is cleared; each warning there corresponds to a real defect, not pedantry. If a change doesn't move the curl number or clear a warning, it wasn't the bottleneck — revert it and move on rather than stacking cargo cult.
What tuned actually feels like
Concrete expectations on an N100 with 8GB and the tuning above: login to usable Files view in about a second, folder navigation at 300–500ms, sync propagation in 1–2 seconds with notify_push, uploads at line speed. What tuning cannot fix: the web UI's initial JS payload (a few MB — first load on a slow connection drags), server-side encryption's throughput cost (skip it; use full-disk encryption instead), and apps like Talk on the same small box competing for CPU. Disable the apps you don't use — each one you drop shaves startup work; Photos, Activity and Talk are the heavy defaults worth auditing.
If you only wanted fast file sync
A blunt opinion: many slow-Nextcloud complaints come from people using 5% of a groupware platform to sync files. If that's you, Syncthing moves files peer-to-peer at disk speed with none of this tuning — the trade-offs run through Nextcloud vs Syncthing, and more options sit in the file sync category. Nextcloud earns its weight when you actually use calendars, contacts, shares, and office editing.
Bottom line
In order of effect: Redis locking plus APCu, real cron every 5 minutes, Postgres with the missing indices added, pregenerated capped previews, notify_push, then OPcache trimming. The first two take twenty minutes and deliver most of the perceived speedup; the rest is an evening. Past that, stop tuning — you've hit the platform's floor, and the remaining latency is the price of running a full groupware suite on 6 watts.