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

rebuilding nextcloud and meaning it this time

A third attempt at self-hosting Nextcloud, this time with a separate database, Redis for locking, and the cron job actually wired up.

A server rack in a homelab

This is my third Nextcloud. The first one I installed with the all-in-one snap, used twice, and abandoned. The second one ran on SQLite, which was fine right up until it absolutely was not, and the file locking errors that followed taught me a lesson I should have learned from the docs. So this time I did it properly, and I am writing it down so that future me does not have to rediscover any of this.

The bits that actually matter

The headline mistake people make, me included, is running Nextcloud against SQLite and then being surprised when it falls over under any concurrency. So this install gets PostgreSQL, on its own container, with its own volume that I actually back up.

The second thing is Redis, and not as a cache afterthought. Nextcloud uses it for transactional file locking, and without it you get the dreaded "file is locked" errors that never quite clear themselves. With Redis configured for locking those simply stopped happening.

services:
  db:
    image: postgres:15
    environment:
      POSTGRES_DB: nextcloud
      POSTGRES_USER: nextcloud
    volumes:
      - ./pgdata:/var/lib/postgresql/data
  redis:
    image: redis:7-alpine
  app:
    image: nextcloud:27-apache
    depends_on: [db, redis]
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - ./data:/var/www/html

I put it behind Caddy for TLS, because I am not hand-rolling certbot renewals in 2023 when Caddy does it for free with one line.

A homelab dashboard showing storage usage

The cron job nobody mentions until it breaks

Nextcloud has background jobs: cleaning up trash, generating previews, running the housekeeping that stops the database bloating. By default it runs them via AJAX, piggybacked on user page loads, which means they barely run at all and your instance slowly rots. The admin panel even nags you about it.

So I set the background job mode to "Cron" and added a systemd timer on the host that runs cron.php every five minutes. The moment I did that the preview generation caught up overnight and the storage report started telling the truth.

The other quiet win was setting maintenance_window_start so the heavy nightly jobs run at four in the morning rather than whenever they feel like it. Small thing. Makes the whole instance feel less like it's thinking about something else when you're trying to use it.

It has been a week. It is fast, the mobile client syncs without sulking, and I have not seen a single locking error. Third time, as it turns out, is the charm, provided you give it a real database and stop pretending SQLite is a multi-user filesystem.