My old Nextcloud was the textbook bad install: a tarball dropped onto Apache, SQLite for the database because the docs let me, and PHP settings left at whatever the distro shipped. It worked, in the sense that files synced eventually, but the web UI took several seconds to load every page and the mobile client timed out more often than it should. I had been blaming the network. It was not the network.
This time I rebuilt it properly, and wrote down what "properly" means so I do not relearn it in two years.
the actual problems with the old setup
Three things were dragging it down, and only one was obvious.
SQLite was the obvious one. It is fine for a single user with a handful of files, and miserable the moment two clients sync at once, because writes serialise and everything queues behind them. Nextcloud's own docs say not to use it for anything real, and they are right.
The second was no caching layer. Nextcloud does an enormous amount of small, repeated work: checking file locks, reading config, resolving app routes. Without Redis it does all of that against the database every single request.
The third was PHP. The default memory_limit and opcache settings are conservative, and Nextcloud is a big PHP app that benefits a lot from opcache being given room to breathe.
the new shape
I moved everything to Docker Compose, which makes the moving parts explicit and the upgrades a great deal less frightening. The stack is four containers: the Nextcloud FPM image, PostgreSQL, Redis, and an nginx container in front. Traefik handles TLS and routing for the wider homelab, so Nextcloud just exposes its port and lets Traefik terminate the certificate.
A trimmed version of the compose file:
services:
db:
image: postgres:13-alpine
environment:
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
volumes:
- ./data/db:/var/lib/postgresql/data
redis:
image: redis:6-alpine
app:
image: nextcloud:21-fpm
depends_on:
- db
- redis
environment:
POSTGRES_HOST: db
REDIS_HOST: redis
volumes:
- ./data/nextcloud:/var/www/html
web:
image: nginx:alpine
depends_on:
- app
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./data/nextcloud:/var/www/html:ro
Nextcloud 21 had only just landed, so this is current as of this month. The FPM image rather than the Apache one is deliberate: nginx in front does the static file serving and passes PHP requests to FPM over the network, which is the layout the performance docs recommend.
the bits that bite you
A few things caught me out, and they are the reason I am writing this down.
Redis needs to be configured for both caching and file locking, not just one. In config.php:
'memcache.local' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' => [
'host' => 'redis',
'port' => 6379,
],
The locking line is the one people forget, and it is the one that fixes the "another process is currently working on this file" errors during big syncs.
Trusted domains and the overwrite settings matter the moment you sit behind a reverse proxy. If you do not tell Nextcloud what its real public URL is, it generates internal links pointing at the container's port and the web UI subtly breaks:
'overwriteprotocol' => 'https',
'overwrite.cli.url' => 'https://cloud.example.com',
'trusted_proxies' => ['172.18.0.0/16'],
And the security scan in the admin panel will nag you about HSTS headers and the .well-known redirects for CalDAV and CardDAV until you add them to the nginx config. Worth doing, because those redirects are what let calendar and contacts autodiscovery work without clients needing the full path.
was it worth it
Page loads dropped from several seconds to comfortably under one. The mobile client stopped timing out. A bulk upload of a few thousand photos, which used to lock the database for the duration, now runs in the background without the web UI noticing.
The honest lesson is that none of this was Nextcloud being slow. It was me asking SQLite to do a job it was never meant for and skipping the caching layer the docs told me to add. The software was fine. I just had not read the manual the first time, and the second time round, with everything in a compose file I can read top to bottom, it took an evening rather than a weekend.