Self-Hosting 📖 11 min read

Nextcloud Self-Hosted Cloud

With a hosted cloud drive, your files live on someone else's servers under terms they can change. Nextcloud gives you file sync and sharing, calendars and contacts on a server you control, in exchange for running, updating and backing it up yourself. This guide sets up Nextcloud with MariaDB and Redis in Docker Compose, puts it behind Nginx with HTTPS, and covers background jobs, sync clients, backups, upgrades and the problems people run into most.

What You Need

  • Hardware: A 64-bit server with enough disk for your files and their backups. Nextcloud recommends at least 512 MB of RAM per PHP process, and MariaDB and Redis need memory on top of that.
  • Software: Ubuntu 24.04 or 26.04 LTS, or Debian 13, with Docker Engine and the Compose plugin; Nginx and Certbot on the host; a domain name pointing at the server
  • Estimated Time: 1-2 hours, plus time for DNS changes if the domain is new

What you give up and what you get:

  • Giving up: a service someone else keeps running, backed up and patched, with free storage and no setup
  • Giving up: some polish; the sync clients and mobile apps work, but conflict handling and offline use are simpler than in the big commercial services
  • Getting: your files on hardware you control, with no provider scanning or reusing them under its own terms
  • Getting: storage limited only by the disks you add
  • Getting: full control over users, sharing and which apps are installed

Versions: written for the official Nextcloud Docker image tag nextcloud:34-apache (the image's stable/production line when this page was updated; latest pointed at 35), mariadb:11.8 and redis:8-alpine. MariaDB 11.8 is the recommended database version in the Nextcloud 34 system requirements. Pin it rather than using mariadb:lts: that tag had already moved to MariaDB 12.3, which isn't on the Nextcloud 34 list.

⚠️ Know how sync conflicts work

If the same file is changed on two devices before either has synced, the desktop client keeps both versions and marks one as a conflicted copy; there is no automatic merge, so you compare and clean them up yourself. Check the client's status icon now and then, especially on laptops that are often offline, and keep files that several people edit in Nextcloud Office or another collaborative editor instead of syncing them as files.

The Compose File

This follows the "Base version - apache" example in the image's README, with versions pinned, Redis added for caching and file locking, and the port bound to localhost so that only the reverse proxy can reach it. Install Docker first if needed (see the Docker Installation Guide).

Editor window showing the configuration from the section The Compose File
The database service, with the transaction isolation Nextcloud needs.

Create a directory such as /srv/nextcloud with a .env file for the passwords, readable only by you:

# /srv/nextcloud/.env
MYSQL_ROOT_PASSWORD=replace-with-a-long-random-password
MYSQL_PASSWORD=replace-with-another-long-random-password

Then compose.yaml in the same directory:

services:
  db:
    image: mariadb:11.8
    command: --transaction-isolation=READ-COMMITTED
    restart: unless-stopped
    volumes:
      - db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud

  redis:
    image: redis:8-alpine
    restart: unless-stopped

  app:
    image: nextcloud:34-apache
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"
    depends_on:
      - db
      - redis
    volumes:
      - nextcloud:/var/www/html
    environment:
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_HOST=db
      - REDIS_HOST=redis
      - NEXTCLOUD_TRUSTED_DOMAINS=cloud.example.com
      - OVERWRITEPROTOCOL=https

volumes:
  db:
  nextcloud:
  • --transaction-isolation=READ-COMMITTED is required by Nextcloud for MariaDB and MySQL.
  • Setting all of MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD and MYSQL_HOST configures the database automatically, so the installer won't ask for it.
  • REDIS_HOST makes the image configure Redis for distributed caching and file locking. The image also enables APCu as the local cache, so you don't edit config.php for either.
  • NEXTCLOUD_TRUSTED_DOMAINS is your public hostname. OVERWRITEPROTOCOL=https tells Nextcloud it is served over HTTPS by the proxy, so it generates https:// links.
  • Everything Nextcloud stores outside the database, including user files in data/ and config/config.php, lives in the nextcloud volume. The README shows how to use separate volumes or bind mounts if you want the data on a specific disk, for example an NFS share from the TrueNAS Storage Setup.

Initial Setup

Terminal window with the commands from this section
Start the stack and follow the app's log while Nextcloud finishes setting up.
cd /srv/nextcloud
docker compose up -d
docker compose logs -f app

The first start copies Nextcloud into the volume, which takes a little while; wait until the log shows Apache running. Because the port is bound to 127.0.0.1, set up the reverse proxy below and then open https://cloud.example.com to create the admin account. To look at it before the proxy exists, tunnel the port from your computer with ssh -L 8080:127.0.0.1:8080 you@server and open http://localhost:8080.

Reverse Proxy with SSL

Nextcloud should only be reachable over HTTPS. The Nginx Web Server Setup covers installing Nginx and getting a Let's Encrypt certificate with Certbot. With a certificate for cloud.example.com in place, a server block like this proxies to the container:

server {
 listen 80;
 server_name cloud.example.com;
 return 301 https://$host$request_uri;
}

server {
 listen 443 ssl;
 http2 on;
 server_name cloud.example.com;

 ssl_certificate /etc/letsencrypt/live/cloud.example.com/fullchain.pem;
 ssl_certificate_key /etc/letsencrypt/live/cloud.example.com/privkey.pem;

 client_max_body_size 512M;

 location / {
 proxy_pass http://127.0.0.1:8080;
 proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header X-Forwarded-Proto $scheme;

 proxy_connect_timeout 600;
 proxy_send_timeout 600;
 proxy_read_timeout 600;
 }
}

http2 on; is the syntax for nginx 1.25.1 and later; older versions use listen 443 ssl http2; instead. client_max_body_size matches the image's default PHP upload limit of 512M. The Apache image takes the client address from X-Real-IP when the request comes from a private-range proxy, so Nextcloud's logs show real client IPs. Run sudo nginx -t and reload Nginx after adding the file. Nextcloud's reverse proxy documentation covers other setups.

If you'd rather not expose Nextcloud to the internet at all, keep it LAN-only and reach it remotely over a VPN; the Complete Self-Hosting Guide compares the options.

Configure Trusted Domains

Nextcloud refuses requests for hostnames that aren't in trusted_domains. NEXTCLOUD_TRUSTED_DOMAINS sets it at installation; to add a name later, use occ, Nextcloud's command-line tool:

docker compose exec -u www-data app php occ config:system:set trusted_domains 1 --value=cloud.example.com

Caching and Background Jobs

Check the Caching Setup

The image writes the APCu and Redis settings for you. To see the merged configuration (passwords are hidden by default):

docker compose exec -u www-data app php occ config:list system

You should see memcache.local set to APCu and memcache.distributed and memcache.locking set to Redis.

Background Jobs

Nextcloud runs maintenance tasks in the background. The background jobs documentation recommends system cron every 5 minutes. Switch the method to cron:

docker compose exec -u www-data app php occ background:cron

Then add this to the host's crontab (crontab -e as a user allowed to run Docker), adjusting the path:

*/5 * * * * docker compose -f /srv/nextcloud/compose.yaml exec -T -u www-data app php -f /var/www/html/cron.php

Administration settings → Basic settings shows when background jobs last ran.

Desktop and Mobile Sync

  • Desktop: install the Nextcloud desktop client from nextcloud.com (Windows, macOS and Linux)
  • iOS/Android: install the Nextcloud Files app from the app stores
  • WebDAV: any WebDAV client can connect

The server address is your domain, for example https://cloud.example.com. For the conflict behaviour, see the note at the top of this page.

Calendar and Contacts

Enable the Calendar and Contacts apps in Nextcloud.

Sync to devices using CalDAV/CardDAV:

  • iOS: add a CalDAV account and a CardDAV account under the "Other" account type in Settings
  • Android: use the DAVx⁵ app
  • Thunderbird: add the calendar and address book as network (CalDAV/CardDAV) resources

Server URL for both:

https://cloud.example.com/remote.php/dav

Key Apps

The app store has a lot of apps, maintained to very different standards. Before installing one, check when it was last updated and that it supports your Nextcloud version, and install apps one at a time so you know what to remove if something breaks. Commonly used apps:

  • Nextcloud Office - collaborative document editing. It needs a Collabora Online server, either a separate container or the built-in CODE server app.
  • Talk - chat and video calls. Calls between people on different networks usually need a TURN server.
  • Deck - Kanban boards.
  • Notes - Markdown notes.
  • Photos - photo browsing; the mobile apps can upload new photos automatically.

Backup Strategy

There's no provider keeping copies for you any more. The Nextcloud backup documentation lists what to keep: the config, data and theme folders, custom apps if you have any, and the database, taken while the server is in maintenance mode.

Terminal window with the commands from the section Backup Strategy
Maintenance mode on, then a database dump from inside its container.
cd /srv/nextcloud

# Stop changes while the backup runs
docker compose exec -u www-data app php occ maintenance:mode --on

# Database dump (the db container already has the password in its environment)
docker compose exec -T db sh -c 'exec mariadb-dump --single-transaction -u nextcloud -p"$MYSQL_PASSWORD" nextcloud' > nextcloud-db.sql

# Files: config, data, themes and custom apps
docker compose exec -T app tar czf - -C /var/www/html config data themes custom_apps > nextcloud-files.tar.gz

docker compose exec -u www-data app php occ maintenance:mode --off

Copy both files to another machine or off-site storage, keep several generations, and test a restore now and then. The 3-2-1 approach and tools such as restic are covered in the Complete Self-Hosting Guide. For large data folders, a file-level backup tool that copies only changes will be faster than a full tar each time.

Restoring From These Backups

The restore documentation needs all three parts, config, data and database, and the database has to be emptied before the dump goes back in. Restore into the same nextcloud image version the backup came from, and only then upgrade. This replaces the current database, so take a fresh backup first if there's anything in it you might want.

cd /srv/nextcloud
docker compose exec -u www-data app php occ maintenance:mode --on

# Empty the database, then load the dump
docker compose exec -T db sh -c 'exec mariadb -u root -p"$MYSQL_ROOT_PASSWORD" -e "DROP DATABASE nextcloud; CREATE DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci"'
docker compose exec -T db sh -c 'exec mariadb -u nextcloud -p"$MYSQL_PASSWORD" nextcloud' < nextcloud-db.sql

# Put config, data, themes and custom apps back
docker compose exec -T app tar xzf - -C /var/www/html < nextcloud-files.tar.gz

docker compose exec -u www-data app php occ maintenance:mode --off
docker compose exec -u www-data app php occ files:scan --all

Extracting the archive overwrites the files it contains but leaves files created after the backup in place; files:scan brings the database back in line with what's on disk. If the backup is older than what the sync clients have, the docs add occ maintenance:data-fingerprint, which makes clients upload what the server is missing instead of deleting it. It can cause conflict prompts on clients, so use it only in that situation.

Updates and Upgrades

Updating the container means pulling a new image and recreating it. On start, the image compares its version with the one in the volume and runs the upgrade automatically.

Terminal window with the commands from the section Updates and Upgrades
Updating is pull, up, and watching the log while the upgrade runs.
cd /srv/nextcloud
docker compose pull
docker compose up -d
docker compose logs -f app

With the tag pinned to 34-apache, this brings in the latest 34.x release. To move to a new major version, take a backup, then change the tag to the next major (35-apache) and run the same commands. The README is explicit that you can only upgrade one major version at a time, so going from 34 to 36 means stopping at 35 first.

Common Issues

Upload Size Limits

Three limits apply: the image's PHP limit (512M by default, set with the PHP_UPLOAD_LIMIT environment variable on the app service), the image's Apache request-body limit (APACHE_BODY_LIMIT, 1 GiB by default, where 0 means unlimited) and Nginx's client_max_body_size. Raise them together, as described in the image README and the big file upload documentation. The desktop and mobile clients upload large files in chunks, so these limits mostly affect uploads through the browser.

"Access through untrusted domain"

The hostname you used isn't in trusted_domains. Add it with the occ command above.

Redirects to http:// or a login loop behind the proxy

Check that OVERWRITEPROTOCOL=https is set and that Nginx passes X-Forwarded-Proto. Recreate the container (docker compose up -d) after changing environment variables.

Warnings in the admin overview

Administration settings → Overview runs Nextcloud's own security and setup checks and, where one exists, links to the related documentation for a warning; the admin manual's warnings page explains the common ones. Work through it after installing and after each major upgrade.

Sync Conflicts

Conflicted copies appear when the same file changed in two places between syncs. Compare the two versions, keep the right one and delete the other. Redis file locking prevents simultaneous writes on the server, but it can't prevent conflicts from offline edits.

Nextcloud is not as polished as the large commercial services, and it is one more server to maintain. For personal files, shared family photos and calendars, it's a workable replacement if you're prepared to run updates and test your backups.