Self-Host Your Own Drive and Photo Backup with Nextcloud

Self-Host Your Own Drive and Photo Backup with Nextcloud

Free cloud storage fills up quietly, and phone photo backup fails in the most forgettable way there is. The account crosses its limit, the app puts a small warning icon somewhere nobody looks, and uploading simply stops. Weeks later somebody goes hunting for a picture, does not find it, and works out that a month and a half of photographs exist on exactly one device. Nothing is lost while that handset survives. That is the entire margin: one dropped phone. Self-hosting is the other answer to the same problem, and Nextcloud is the shape it usually takes — your own drive, your own photo backup, running on a machine in your own flat, limited by the disks you bought rather than a plan you did not choose. It is both better and more work than most write-ups admit, and the work does not arrive on day one. It arrives in month four, which is why this guide spends as long on backups and background jobs as on installation.

what you get, and what you take on

Comparison: Nextcloud gives sync, photo upload and your own storage; in exchange you take on patching, disk monitoring and backups.
You are not removing a dependency. You are moving it onto yourself.

Nextcloud is the self-hosted equivalent of a cloud drive. Files sync between laptop and server, phones upload photos in the background, you can share a link, and there are apps for calendars, contacts and notes on top. Storage is limited by the drives you own rather than a plan, which is the appeal once a family photo library outgrows a free tier.

The part most guides skate over is the trade. You are not removing a dependency, you are moving it onto yourself. Nobody else patches this server, or notices that the disk is nearly full, or that the nightly backup stopped running a fortnight ago. That trade is worth making, but make it with your eyes open rather than discovering it in month four.

Self-hosting your photos without a working, tested backup is worse than the cloud service you left. A single drive in your flat is one failure away from nothing.

install approaches and their trade-offs

Table of four Nextcloud install routes: All-in-One container, official image with Compose, Snap package and a source install, with strengths and catches.
If the box already terminates HTTPS for other services, use the official image in your own Compose file.
Flow: a reverse proxy terminates TLS in front of Nextcloud, which uses PostgreSQL for its database and Redis for locking and caching.
Redis removes the ‘file is locked’ errors that make sync feel broken.

There are four routes people take, and the right one depends mostly on how much you want to understand versus how quickly you want to be finished.

ApproachGood forCatch
All-in-One containerFastest working install, handles updates and backup for youWants control of its own stack; awkward to slot into an existing proxy setup
Official image plus your own Compose fileFull control, fits an existing reverse proxyYou wire up database, cache and cron yourself
Snap packageSimple on Ubuntu, self-updatingOpinionated paths, less room to tune
Source install on a LAMP stackMaximum control, best for learningMost maintenance, most ways to get it subtly wrong

I would reach for the official image inside your own Compose project whenever the box already terminates HTTPS for other services, because a second thing fighting over port 443 is a problem you create for yourself. If this is your first self-hosted application, All-in-One is a kinder starting point and you can migrate later.

services:
  nextcloud-db:
    image: postgres:16
    container_name: nextcloud-db
    restart: unless-stopped
    environment:
      POSTGRES_DB: nextcloud
      POSTGRES_USER: nextcloud
      POSTGRES_PASSWORD: ${NC_DB_PASSWORD}
    volumes:
      - nc-db:/var/lib/postgresql/data

  nextcloud-cache:
    image: redis:7-alpine
    container_name: nextcloud-cache
    restart: unless-stopped

  nextcloud:
    image: nextcloud:35-apache
    container_name: nextcloud
    restart: unless-stopped
    depends_on:
      - nextcloud-db
      - nextcloud-cache
    environment:
      POSTGRES_HOST: nextcloud-db
      POSTGRES_DB: nextcloud
      POSTGRES_USER: nextcloud
      POSTGRES_PASSWORD: ${NC_DB_PASSWORD}
      REDIS_HOST: nextcloud-cache
      NEXTCLOUD_TRUSTED_DOMAINS: cloud.example.com
      TRUSTED_PROXIES: 192.168.1.50   # your reverse proxy's address as this container sees it
      OVERWRITEPROTOCOL: https
      PHP_MEMORY_LIMIT: 512M
      PHP_UPLOAD_LIMIT: 4G
    volumes:
      - /srv/appdata/nextcloud/html:/var/www/html
      - /srv/data/nextcloud:/var/www/html/data

volumes:
  nc-db:

Check Nextcloud’s release schedule before copying the tag; each major version is supported for about a year. If you installed from an older copy of this guide that pinned nextcloud:30-apache, don’t jump straight to the current tag: step through each major version in turn (31, 32, 33, 34, 35), with a backup before each step.

database choice, briefly

Use PostgreSQL or MariaDB. Both are well supported and both will outlive your interest in this project. SQLite is offered and is fine for a single user kicking the tyres, but it falls over under concurrent sync clients, and migrating away from it later is an afternoon you will resent. Pick a real database on day one even if you are the only user.

Add Redis too. Nextcloud uses it for file locking and caching, and without it you will eventually meet the “file is locked” errors that make sync feel broken. It costs very little memory and removes a whole class of complaint.

HTTPS and the reverse proxy requirement

nginx fragment with client_max_body_size 4G, proxy_request_buffering off, and 301 redirects for the CardDAV and CalDAV well-known paths.
Nextcloud over plain HTTP is not a configuration, it is a mistake.

Nextcloud over plain HTTP is not a configuration, it is a mistake. Mobile apps expect TLS, some clients refuse to connect without it, and every password and file crosses your network in the clear. Put a reverse proxy in front — Caddy, Nginx or Traefik — terminate TLS there, and forward to the container.

You need a real hostname for a certificate, which means a domain pointing at your setup. If your connection has no public address, use a DNS provider that supports the challenge over DNS, or keep the whole thing reachable only over a private VPN and use an internal certificate. The proxy configuration follows the pattern in the Nginx web server setup guide, with two additions Nextcloud specifically needs: a generous client body size so large uploads are not rejected, and the forwarded-header and redirect rules that make service discovery work.

# Nginx fragments Nextcloud cares about
client_max_body_size 4G;
proxy_request_buffering off;

location = /.well-known/carddav {
    return 301 $scheme://$host/remote.php/dav;
}
location = /.well-known/caldav {
    return 301 $scheme://$host/remote.php/dav;
}

Then open Settings, Administration, Overview in Nextcloud itself. It runs a self-check and lists exactly which headers, redirects and limits it is unhappy about. Work that list down to empty before you invite anyone else onto the server.

phone photo auto-upload, the feature that justifies all of this

Checklist for Nextcloud phone upload: enable camera auto-upload, restrict to Wi-Fi, use a date-based folder, exclude the app from battery optimisation, and leave delete-after-upload off.
This is the feature that justifies the whole setup.

Install the Nextcloud app on the phone, sign in, then turn on auto-upload for the camera folder and for WhatsApp images if you want those too. Set it to upload only on Wi-Fi unless you enjoy paying for mobile data twice, and choose a target folder structured by date so the library stays browsable.

Two settings deserve thought. “Delete after upload” sounds tidy and I would leave it off, because a sync that misbehaves should not be able to remove the original. And on Android, battery optimisation will happily kill the app and stop your uploads for days; exclude Nextcloud from optimisation on every phone you set up. This one fails silently, so open the app on each handset once a month and check the last upload time rather than assuming.

Once photos land on the server, they are ordinary files in an ordinary folder, which means other things can use them. Point a media server at the same tree and family footage recorded on a phone shows up as an album on the living room TV without anybody copying anything.

external storage, for the drive you already have

Terminal: occ status, files:scan --all, db:add-missing-indices and maintenance:mode --on, each run with docker exec as www-data.
External storage is not noticed when other programs change it until a files:scan runs.

You do not have to copy your existing files into Nextcloud’s data directory. The External Storage app mounts a local folder, an SMB share or S3-compatible object storage as a folder inside Nextcloud, leaving the files where they are. This is the tidy way to expose an existing media drive without duplicating a single byte.

Worth knowing before you rely on it: files changed outside Nextcloud are not noticed until something scans them, so add a periodic files:scan if other programs write there. Permissions have to line up with the web server’s user, or the mount appears empty with no explanation. And Nextcloud’s versioning and trash bin do not apply to external mounts the way they do to internal storage, so a deletion there is a real deletion.

# Useful occ commands, run inside the container as the web user
docker exec -u www-data nextcloud php occ status
docker exec -u www-data nextcloud php occ files:scan --all
docker exec -u www-data nextcloud php occ db:add-missing-indices
docker exec -u www-data nextcloud php occ maintenance:mode --on

performance basics that fix most complaints

Four Nextcloud performance settings: raise PHP memory to 512M, align upload limits, configure Redis and APCu, and switch background jobs to Cron.
A sluggish Nextcloud is usually configuration, not hardware.
Root crontab line running Nextcloud's cron.php every five minutes via docker exec, then occ background:cron to switch modes.
On Ajax, maintenance only runs when someone loads a page, which mostly means never.

A sluggish Nextcloud is usually not a hardware problem. Four settings account for most of it.

  • PHP memory limit. The default is too small for photo previews and large uploads. Raising it to 512M is a reasonable starting point.
  • Upload limits. PHP’s own upload size and your proxy’s body size both have to allow the largest file you will ever send. Whichever is smallest wins, silently.
  • Memory caching. With Redis configured for locking and APCu for local cache, the interface stops feeling laggy on every page load.
  • Preview generation. Generating thumbnails on demand for a folder of thousands of photos is painful. Pre-generate them on a schedule instead.

background jobs: move off Ajax immediately

Out of the box, Nextcloud runs its maintenance tasks when someone loads a page. That means they run erratically, or not at all if nobody visits for a week, and then a mysterious backlog appears. Switch the background job mode to Cron in the admin settings and give it a real schedule:

# Every five minutes, as root's crontab on the Docker host
*/5 * * * * /usr/bin/docker exec -u www-data nextcloud php -f /var/www/html/cron.php >/dev/null 2>&1

# Switch background jobs to Cron mode (same as the admin setting), then confirm the "last job ran" time on the Overview page
docker exec -u www-data nextcloud php occ background:cron

Then verify the next morning. A scheduled job you never confirmed is a job that might not exist, and Nextcloud’s overview page will tell you plainly when cron last ran.

backing up both halves, or you have backed up nothing

Backup flow: turn on maintenance mode with a trap to turn it off, dump the PostgreSQL database, archive the config folder, and snapshot the data directory in the same window.
Until you have restored once, you have a backup procedure, not a backup.

This is the part I would put in bold if the house style allowed it. Nextcloud is two things: files on disk and a database that describes them. A copy of the files without the database restores to a server that does not know those files exist. A copy of the database without the files restores to a catalogue of nothing. You need both, taken close together in time.

#!/usr/bin/env bash
set -euo pipefail

STAMP=$(date +%F)
OUT=/srv/backup/nextcloud
mkdir -p "$OUT"

docker exec -u www-data nextcloud php occ maintenance:mode --on
# Leave maintenance mode on exit, even if a step below fails
trap 'docker exec -u www-data nextcloud php occ maintenance:mode --off' EXIT

docker exec nextcloud-db pg_dump -U nextcloud nextcloud > "$OUT/db-$STAMP.sql"
tar -czf "$OUT/config-$STAMP.tar.gz" /srv/appdata/nextcloud/html/config
# Run the rsync snapshot of /srv/data/nextcloud here, inside the same window

echo "dumped database and config for $STAMP"

The data directory itself is far too large for a nightly tar, so it goes through incremental snapshots instead, using the script in my rsync backup guide. Call that snapshot from the marked line so it runs inside the same maintenance window, and the database and the files describe the same moment. The trap line switches maintenance mode back off whenever the script exits, including when a step fails, so a failed dump can’t leave the server locked for everyone.

Restore once, deliberately, into a scratch container. Until you have done that you have a backup procedure, not a backup.

common mistakes

Five Nextcloud mistakes: backing up files without the database, leaving Ajax jobs, choosing SQLite, no admin two-factor, and skipping major versions.
The application is only as private as the machine under it.
  • Backing up the files but not the database. Probably the most common self-hosting regret there is, and it only surfaces during a restore.
  • Leaving background jobs on Ajax. Previews, cleanups and notifications then run whenever, which mostly means never.
  • Choosing SQLite because the wizard offered it, then migrating under pressure when two laptops sync at once.
  • Skipping two-factor authentication on the admin account while the service is reachable from the internet.
  • Jumping multiple major versions at once. Upgrade one release at a time, with a snapshot you can roll back to before each step.
  • Never hardening the host underneath. The application is only as private as the machine it sits on; my Ubuntu server security checklist is the baseline I apply before anything faces outward.

FAQ

is Nextcloud too heavy for a Raspberry Pi?

It runs, and for one or two users with modest libraries it is usable. The Pi’s weak points are preview generation for large photo collections and slow storage over USB. A Pi 5 with a decent SSD is a reasonable small deployment. For four people syncing laptops and phones, more memory and real SATA will make everyone happier.

can I skip the reverse proxy on my home network?

You can technically run it on plain HTTP inside your LAN, and some mobile clients will resist you the whole way. I would not. Even inside the flat, credentials crossing the network unencrypted is a habit worth not forming, and a proxy with a certificate is an hour of work once. If you truly never want it exposed, combine an internal certificate with VPN-only access.

what happens if my server dies with the only copy of my photos?

Then you have lost them, and this is why the backup section is not optional padding. Self-hosting removes someone else’s redundancy and asks you to supply your own. Minimum viable arrangement: the server, a local backup drive, and one copy that lives somewhere other than your flat. A drive at a relative’s house that you swap on visits counts perfectly.

how do I reach it from outside without exposing it?

Use a mesh VPN rather than a forwarded port; home lab networking: static IPs and VLANs explains why, and covers giving the server a stable local address.

Get auto-upload working on one phone this weekend and leave the rest of the apps alone until that has run for a month. The goal is not a self-hosted everything, it is never again having a month and a half of photographs living on one handset.

Update history: replaced the end-of-life Nextcloud 30 image with 35 and added an upgrade-path note, named the containers so the occ, cron and backup commands work, added reverse-proxy settings, made the backup script always leave maintenance mode, and updated Sources.

Sources