This is at least the third time I have set up Nextcloud, and the first two attempts both ended the same way: working, then slowly degrading, then quietly abandoned in favour of the very cloud services I was trying to escape. The difference this time is that I sat down and did it properly, which is to say I read the documentation before rather than after things went wrong.
The reason it failed the previous times was always the same shape. I used the all-in-one approach, or worse the snap, got something running in twenty minutes, felt clever, and then hit the wall the first time I wanted to do anything the convenient packaging had decided for me. SQLite under the hood, no proper caching, a reverse proxy bolted on as an afterthought, and backups that I intended to set up later and never did. It worked until it didn't, and when it didn't, untangling it was harder than starting again.
the parts that matter
Doing it properly means treating Nextcloud as what it actually is: a PHP application that needs a real database, a cache, a web server, and somewhere to put the data, all of which you should choose deliberately rather than accept by default.
The database is the first decision. SQLite is fine for a single user kicking the tyres and miserable for anything real. I went with PostgreSQL, on its own container, with its own volume, so the database has an existence independent of the application. When I inevitably tear Nextcloud down and rebuild it a fourth time, the data outlives the app.
The second decision is caching, and this is the one I skipped every previous time. Nextcloud without a memory cache spends an enormous amount of effort on file locking and metadata it could be holding in RAM, and you feel it as sluggishness that gets worse as your file count grows. Redis fixes both the caching and the transactional file locking. Adding it is a few lines of config and it is the single biggest reason this install feels fast where the old ones felt like wading through treacle.
'memcache.local' => '\OC\Memcache\APCu',
'memcache.distributed' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' => [
'host' => 'redis',
'port' => 6379,
],
The third decision is the reverse proxy. Nextcloud sits behind a proper proxy that terminates TLS, handles the certificate renewal, and forwards the right headers so Nextcloud knows it is being served over HTTPS. Get the forwarded headers wrong and you spend an evening chasing redirect loops and "untrusted domain" errors, which I have done before and have no wish to do again. The trusted domains and the overwrite settings in the config need to match what the proxy is actually presenting to the world.
the bit I always neglected
Backups. Every previous Nextcloud of mine died with no usable backup, because the backup was always the thing I would do once everything else was perfect, and everything else was never perfect.
This time the backup existed before I uploaded a single file. It is not clever. It is a scheduled job that puts Nextcloud into maintenance mode, dumps the PostgreSQL database, snapshots the data volume, and copies both off the machine to separate storage.
#!/usr/bin/env bash
set -euo pipefail
docker exec nextcloud php occ maintenance:mode --on
docker exec nextcloud-db pg_dump -U nextcloud nextcloud | gzip > "/backups/db-$(date +%F).sql.gz"
rsync -a --delete /srv/nextcloud/data/ /backups/data/
docker exec nextcloud php occ maintenance:mode --off
The maintenance mode around the dump matters. You want the database and the files to be consistent with each other, and the simplest way to guarantee that is to stop writes for the few seconds the dump takes. I run it nightly. More importantly, I have actually tested a restore into a throwaway instance, because a backup you have never restored is a hope, not a backup. It came back clean, which is the first time in three attempts I have been able to say that.
is it worth running yourself
Honestly, for a lot of people, no. The hosted options are good, they are cheap, and they do not page you at eleven at night when a certificate fails to renew. If your goal is simply to have your files sync, pay someone and get on with your life.
I run it because I want my data on hardware I control, because I enjoy understanding the whole stack, and because there is a real satisfaction in opening a clean web interface and knowing exactly what is behind it: a database I chose, a cache I configured, a proxy I set up, and a backup I have actually restored. The first two attempts taught me how it fails. This one, built deliberately with every part chosen on purpose, is the one I expect to still be running this time next year. We shall see. I have been confident before.