Ramblings of an aging IT geek
← Ramblings of an aging IT geek
homelab

nextcloud, for the third time, and the setup that finally stuck

My third attempt at self-hosting Nextcloud, this time with Postgres, Redis, a proper reverse proxy and the boring config changes that turned it from sluggish to genuinely usable.

A server rack with cabling

I have installed Nextcloud three times now. The first time it was the snap, which worked until it didn't and then fought me about where its data lived. The second time it was the all-in-one Docker image, which is genuinely fine until you want to deviate from its assumptions by even a degree. This is the third time, with each piece run as a container I actually understand, and it's the first version I trust enough to put my photos and my Obsidian vault behind.

The reason the earlier attempts soured wasn't Nextcloud being bad. It was me treating it as a single black box and then being surprised when the black box had opinions. Once I broke it into its actual parts, database, cache, app, web server, reverse proxy, every problem became a normal, debuggable problem rather than a mysterious one.

the database is not optional

The single biggest performance change was moving off SQLite, which the easy installers quietly give you, and onto Postgres. SQLite is fine for a single user kicking the tyres. The moment you have the desktop client, the mobile client, and a couple of background jobs all touching the database at once, it locks and everything feels like wading through treacle.

services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: nextcloud
      POSTGRES_USER: nextcloud
      POSTGRES_PASSWORD_FILE: /run/secrets/nc_db_pass
    volumes:
      - ./db:/var/lib/postgresql/data
    secrets:
      - nc_db_pass

  redis:
    image: redis:7-alpine
    command: redis-server --save "" --appendonly no

Redis is the other piece nobody mentions until things are slow. Nextcloud uses it for file locking and for caching, and without it you get spurious "another process is editing this file" errors and a lot of unnecessary filesystem churn. The Redis config above deliberately turns off persistence: it's a cache, I don't care if it loses its contents on restart, and not writing to disk keeps it quick.

A homelab shelf with mismatched hardware

the config.php changes that actually matter

After the app is up, there's a short list of settings that move it from "works" to "feels good". These go in config/config.php:

'memcache.local' => '\OC\Memcache\APCu',
'memcache.distributed' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' => [
  'host' => 'redis',
  'port' => 6379,
],
'default_phone_region' => 'GB',
'maintenance_window_start' => 1,

APCu for the local cache, Redis for distributed and locking. The default_phone_region one looks trivial but it silences a permanent warning in the admin panel and stops the contacts app from sulking about phone numbers. The maintenance_window_start tells Nextcloud when it's allowed to run its heavy background jobs, in my case 1am UTC, so it isn't trying to rebuild previews while I'm uploading from my phone.

cron, not ajax

The default background-job mode runs tasks when someone happens to load a page. That means on a personal instance, where days pass between visits, the jobs simply don't run, and then everything you assumed was happening (file scanning, notifications, cleanup) silently isn't. Switch it to real cron:

*/5 * * * * docker exec -u www-data nextcloud-app php /var/www/html/cron.php

Set the mode to "Cron" in the admin settings to match, and the little jobs that keep the instance tidy actually run on schedule.

the reverse proxy and the trusted bits

I put Caddy in front, mostly because its config is short enough to read in one sitting and it handles certificates without me thinking about it. The only Nextcloud-side gotcha is telling it that it's behind a proxy, otherwise it generates http links and the mobile app refuses to connect:

'overwriteprotocol' => 'https',
'overwrite.cli.url' => 'https://cloud.i0.pm',
'trusted_proxies' => ['172.18.0.0/16'],

Get the trusted_proxies range wrong and you'll spend an evening wondering why rate limiting thinks every request comes from the same address. It does, because without that line every request appears to come from the proxy.

was it worth it

Honestly, yes, but with an asterisk. Nextcloud is not lightweight and it never will be. It's a PHP application doing a lot of things, and if you want something that just syncs files you'd be happier with Syncthing and an afternoon off. What Nextcloud gives me that Syncthing doesn't is the photo timeline on my phone, calendar and contacts I own, and a web view I can hand to family members who will never touch a terminal.

The lesson from three attempts is dull and true: the thing isn't fragile, my mental model was. Run it as its real components, give it a proper database and a cache, let cron do its job, and it sits there quietly doing exactly what I wanted the first two times. I'll check back in six months and see whether I still believe that. Given the track record, set a reminder.