Why You Should Own Your Files
I run Nextcloud in my homelab. It replaced Google Drive, Dropbox, and iCloud across every device in my household. The honest version of that story is that it took me two attempts to get it right, and there were weeks where I genuinely considered going back to Google. Nextcloud is powerful, configurable, and mature. It is also the single most maintenance-heavy service in my entire stack.
From the homelab: I replaced Google Drive, Calendar, and Contacts with Nextcloud. It runs on my Proxmox cluster and handles everything from file sync to shared calendars across all my devices. It is one of the first services I recommend to anyone starting their self-hosting journey.
I am telling you this upfront because every other Nextcloud tutorial makes it sound like a quick Docker pull and you are done. You are not. What you are doing is becoming your own cloud provider, and that comes with responsibilities: updates that break plugins, sync conflicts on mobile, cron jobs that silently stop running, and performance issues that creep in as your file count grows. I would still make the same choice today, but I want you going in with your eyes open.
This guide covers a production-grade Nextcloud deployment with Docker Compose, MariaDB, and Redis, not the SQLite demo setup that falls over the moment you add a second user. I will cover the performance tuning, backup strategy, and maintenance reality that most guides conveniently skip.
Prerequisites
Before you start, you will need:
- Docker and Docker Compose installed on your server. If you have not done this yet, follow the Docker installation guide for Ubuntu 24.04.
- A reverse proxy handling TLS termination. Nginx Proxy Manager is the easiest option, and Caddy or Traefik work too. Nextcloud needs HTTPS to function properly — the mobile and desktop sync clients will complain or refuse to connect over plain HTTP.
- A domain name pointing to your server (e.g.,
cloud.yourdomain.com). A free DuckDNS subdomain works if you do not own a domain. - At least 2 GB of RAM free for the Nextcloud stack, and sufficient storage for your files. Nextcloud itself is small, but your data is not. Plan for where your files will actually live.
If you are planning to store a significant amount of data (photos, documents, media), consider mounting an external drive or NAS share for the Nextcloud data directory rather than using the boot disk. I learned this the hard way when my 256 GB NVMe filled up with family photos inside two months. Separate your OS storage from your data storage.
Step 1: Create the Project Directory
Keep things organised from the start. Create a dedicated directory for your Nextcloud deployment:
mkdir -p ~/nextcloud && cd ~/nextcloud
Step 2: Create the Docker Compose File
This is a three-container deployment: Nextcloud (the application), MariaDB (the database), and Redis (the memory cache). Do not use SQLite for anything beyond testing. It does not handle concurrent access, and you will hit locking issues the moment two devices try to sync simultaneously.
Create docker-compose.yml:
services:
nextcloud:
image: nextcloud:35 # pin the major version; see the upgrade note below
container_name: nextcloud
restart: unless-stopped
depends_on:
- db
- redis
environment:
- MYSQL_HOST=db
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- REDIS_HOST=redis
- REDIS_HOST_PASSWORD=${REDIS_PASSWORD}
- PHP_MEMORY_LIMIT=1024M
- PHP_UPLOAD_LIMIT=10G
- NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER}
- NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD}
- NEXTCLOUD_TRUSTED_DOMAINS=${NEXTCLOUD_DOMAIN}
- OVERWRITEPROTOCOL=https
- OVERWRITECLIURL=https://${NEXTCLOUD_DOMAIN}
volumes:
- ./html:/var/www/html
- ./data:/var/www/html/data
ports:
- "8080:80"
db:
image: mariadb:11
container_name: nextcloud-db
restart: unless-stopped
command: --transaction-isolation=READ-COMMITTED
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
volumes:
- ./db:/var/lib/mysql
redis:
image: redis:7-alpine
container_name: nextcloud-redis
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- ./redis:/data
Now create the .env file for your secrets:
NEXTCLOUD_DOMAIN=cloud.yourdomain.com
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=ChangeMeToSomethingStrong
MYSQL_PASSWORD=AnotherStrongPassword
MYSQL_ROOT_PASSWORD=YetAnotherStrongPassword
REDIS_PASSWORD=RedisPasswordHere
Pin the Nextcloud major version. Nextcloud can only be upgraded one major version at a time (33 to 34, then 34 to 35). The container upgrades itself on start when the image is newer than your data, so an unpinned nextcloud:latest that jumps two majors in one pull leaves you with an install that refuses to upgrade. Pin a major tag as above, and when you move up, change it by one major, start the container, let the upgrade finish, and repeat. The tag shown was the current major when this was written; use whichever major you install.
That MariaDB command flag (--transaction-isolation=READ-COMMITTED) is not optional. Nextcloud requires READ-COMMITTED isolation to avoid deadlocks during file operations. (Older examples also added --log-bin=binlog --binlog-format=ROW; the current Nextcloud Docker example no longer includes them.) Skip them and you will get database lock errors under any real usage. This is one of the most common issues in Nextcloud Docker deployments and the cause is almost always a missing MariaDB configuration.
Step 3: Start the Stack
docker compose up -d
Watch the logs to make sure everything starts cleanly:
docker compose logs -f
The first start takes a minute or two as Nextcloud runs its database migrations and creates the admin account. You should see messages about database tables being created. If you see connection refused errors for MariaDB, the database container might not be ready yet. Nextcloud will retry, so give it a moment.
Once it settles, Nextcloud will be accessible on http://your-server-ip:8080. Do not log in yet — set up your reverse proxy first.
Step 4: Configure Your Reverse Proxy
If you are using Nginx Proxy Manager:
- Create a new proxy host for
cloud.yourdomain.com - Point it to your server’s IP on port 8080
- Enable SSL with a Let’s Encrypt certificate
- Turn on Force SSL and HTTP/2
There is one extra step that catches everyone out. Nextcloud needs certain headers passed through correctly. In the Advanced tab of your proxy host, add:
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;
client_max_body_size 10G;
proxy_request_buffering off;
That client_max_body_size line is critical on plain Nginx, which defaults to a 1 MB upload limit, and you will get cryptic errors when syncing anything larger. (Nginx Proxy Manager already ships with a 2000 MB limit, so there it only matters for files bigger than that.) Set it to something reasonable for your use case. I use 10 GB because I sync video files occasionally.
Step 5: First Login and Essential Configuration
Navigate to https://cloud.yourdomain.com and log in with the admin credentials from your .env file. You will land on the Nextcloud dashboard. Before you start uploading files, there are several settings to configure.
Check Redis caching
You do not need to edit config.php for Redis. When REDIS_HOST and REDIS_HOST_PASSWORD are set, the official image writes the Redis settings for the distributed cache and file locking itself, and APCu is already configured as the local cache. Confirm it took:
docker exec -u www-data nextcloud php occ config:system:get memcache.distributed
docker exec -u www-data nextcloud php occ config:system:get memcache.locking
Both should print \OC\Memcache\Redis. If REDIS_HOST_PASSWORD is missing while Redis requires a password, Nextcloud logs authentication errors and pages fail to load, so check that variable first.
Set up the cron job
Nextcloud needs a background task runner. The default is AJAX (runs when someone opens the browser), which is unreliable. Switch to system cron.
In the Nextcloud admin panel, go to Administration Settings > Basic settings and select Cron as the background jobs method.
Then set up the actual cron job on your host:
crontab -e
# Add this line:
*/5 * * * * docker exec -u www-data nextcloud php cron.php
This runs Nextcloud’s background tasks every five minutes. Without it, file scanning, cleanup jobs, and notification emails will not work properly. This is one of the most commonly missed steps, and the symptoms are subtle: things just feel slow and slightly broken without an obvious error.
Configure email
Go to Administration Settings > Basic settings and set up SMTP for email notifications. Nextcloud sends share notifications, password resets, and activity digests via email. Without SMTP configured, shared links will not send notification emails and password resets will silently fail.
Step 6: Adding Users and Connecting Devices
Go to Accounts (called Users before Nextcloud 28) in the top-right menu to create accounts for family members or housemates. Set storage quotas per user if you need to manage disk space.
For sync clients, Nextcloud has apps for every platform:
- Desktop: Download the Nextcloud client for Windows, macOS, or Linux from nextcloud.com
- Mobile: Install from the App Store (iOS) or Google Play (Android)
- WebDAV: Any WebDAV client can connect using
https://cloud.yourdomain.com/remote.php/dav/files/USERNAME/
On first connection, point the client to your domain, log in, and choose which folders to sync. I recommend selective sync rather than syncing everything — your phone does not need a copy of every file on your server.
The Nextcloud mobile app has an automatic photo upload feature that works like Google Photos or iCloud Photo Library. Enable it and your phone photos back up to your own server automatically. This single feature is the reason most people in my household stopped caring about which cloud provider they use — they barely notice the difference.
Step 7: Performance Tuning
A default Nextcloud installation will feel sluggish compared to Google Drive or Dropbox. Those companies throw enormous infrastructure at the problem. You have a single server. But there are several things you can do to close the gap significantly.
PHP memory limit
The image’s default PHP memory limit is 512 MB. The compose file above raises it with PHP_MEMORY_LIMIT=1024M and the upload limit with PHP_UPLOAD_LIMIT=10G. Set them there rather than by editing PHP files inside the container: anything you write inside the container is lost the next time it is recreated, which is every update. Change the values and run docker compose up -d to apply.
Enable preview generation
Install the Preview Generator app from the Nextcloud App Store. Then run initial generation and add it to cron:
# Initial generation (can take hours with many files)
docker exec -u www-data nextcloud php occ preview:generate-all
# Add to crontab for ongoing generation
*/10 * * * * docker exec -u www-data nextcloud php occ preview:pre-generate
Without this, Nextcloud generates thumbnails on-the-fly when you browse folders, which makes the web interface feel painfully slow on large photo libraries.
The Maintenance Reality
Here is what the other guides will not tell you. Nextcloud requires ongoing maintenance, and it is not always smooth.
Updates can break things. Major version upgrades (e.g., 27 to 28) sometimes break apps or change behaviour. I run updates on a maintenance evening, never on a Friday, and I always check the release notes first. If an app I rely on has not been updated for the new version, I wait.
The admin warnings page will haunt you. Go to Administration Settings > Overview and you will likely see a list of warnings about missing indices, recommended settings, and suggested configurations. Work through them. They are not just nags — they affect performance and reliability.
File locking can misbehave. If a sync client crashes mid-upload, files can get stuck in a locked state. Stale locks expire on their own (after an hour by default). To clear them sooner with Redis locking, put Nextcloud in maintenance mode (docker exec -u www-data nextcloud php occ maintenance:mode --on), flush Redis (docker exec nextcloud-redis redis-cli -a 'your-redis-password' FLUSHALL), then turn maintenance mode --off. Knowing this exists will save you a stressful evening.
Back up before every major update. Not “I should back up,” but actually do it. Run docker exec -u www-data nextcloud php occ maintenance:mode --on, back up the database and data directory, then proceed. I have needed to roll back twice. Both times, having that pre-update backup meant the difference between a minor inconvenience and a weekend of data recovery.
Backup Strategy
You need to back up three things: the database, the data directory, and the config.
#!/bin/bash
# nextcloud-backup.sh
set -euo pipefail
# Load MYSQL_PASSWORD from the stack's .env file
set -a; . ~/nextcloud/.env; set +a
BACKUP_DIR="/home/teky/backups/nextcloud"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p "${BACKUP_DIR}"
# Enable maintenance mode
docker exec -u www-data nextcloud php occ maintenance:mode --on
# Dump the database
docker exec nextcloud-db mariadb-dump -u nextcloud -p"${MYSQL_PASSWORD}" nextcloud \
> "${BACKUP_DIR}/db_${TIMESTAMP}.sql"
# Archive config and data
tar czf "${BACKUP_DIR}/nextcloud_config_${TIMESTAMP}.tar.gz" \
-C ~/nextcloud html/config
# Disable maintenance mode
docker exec -u www-data nextcloud php occ maintenance:mode --off
echo "[$(date)] Backup completed"
For the data directory itself, use rsync rather than tar for incremental backups. A full tar of hundreds of gigabytes of files is impractical for nightly runs.
Running Nextcloud in production teaches you skills that translate directly to enterprise file sync platforms like SharePoint and OneDrive administration. Understanding WebDAV, database-backed applications, caching layers, and backup strategies for stateful services are all things that appear on infrastructure and systems administrator job descriptions. The complexity that makes Nextcloud maintenance-heavy is the same complexity you will manage at scale in enterprise roles.
Troubleshooting
Sync client stuck or showing conflicts
The most common sync issue is file locking. Check the Nextcloud log at Administration Settings > Logging for lock-related errors. If files are genuinely stuck, clear the locks as described in the maintenance section above (maintenance mode on, flush Redis, maintenance mode off). For persistent conflicts, the sync client’s activity panel shows exactly which files are affected and why.
“Access through untrusted domain” error
You are accessing Nextcloud via a domain or IP that is not in the trusted domains list. Edit config.php and add the domain to the trusted_domains array. This catches everyone who tries to access via IP address after setting up a domain.
Slow web interface
If the web UI takes several seconds to load folders, check that Redis is connected (look in the admin overview for memory cache status), ensure the cron job is running (check Administration Settings > Basic settings for the last cron execution time), and run the preview generator. These three things account for 90% of performance complaints.
Upload fails for large files
Check your reverse proxy’s client max body size (see Step 4), the PHP upload limit (PHP_UPLOAD_LIMIT in the compose file, 512 MB by default in the official image), and available disk space. All three must allow the file size you are attempting.
What to Do Next
Nextcloud is a significant step toward owning your data. But a file sync platform on its own is just the beginning. Here is where to go from here:
- Set Up Nginx Proxy Manager — the reverse proxy that makes Nextcloud (and every other service) accessible with proper HTTPS. If you skipped this step, go back and do it now.
- WireGuard VPN on Ubuntu — access your Nextcloud instance securely from anywhere without exposing it to the public internet. A VPN is the safest way to reach your homelab remotely.
- Self-Hosted Password Manager with Vaultwarden — if you are self-hosting your files, self-host your passwords too. Runs alongside Nextcloud on the same Docker host.
- Monitor Everything with Uptime Kuma — know when Nextcloud goes down before your family tells you. Set up monitoring for every self-hosted service.
- Install Docker on Ubuntu 24.04 — the foundation this entire stack runs on, if you need to set up another host.
The maintenance overhead is real, but so is the peace of mind. Your files live on your hardware, backed up on your schedule, accessible on your terms. No storage limits, no scanning, no surprise pricing changes. That is worth a maintenance evening once a month.
Watch out for this: Watch your PHP memory limits. The default Docker config often sets them too low, and you will get cryptic errors during large file uploads. I set mine to 1G and the random upload failures disappeared.
Key Takeaways
- Always deploy Nextcloud with MariaDB and Redis, never with SQLite for real usage. SQLite cannot handle concurrent access from multiple devices.
- The MariaDB
--transaction-isolation=READ-COMMITTEDflag is mandatory. Without it, you will get database lock errors under normal use. - Set up system cron for background tasks immediately. The default AJAX method is unreliable and leads to subtle performance issues.
- Configure your reverse proxy to allow large uploads (
client_max_body_size): plain Nginx stops at 1 MB by default. - Nextcloud is maintenance-heavy compared to other self-hosted services. Pin the major version, upgrade one major at a time, budget a monthly maintenance window, and always back up before major version upgrades.
- The automatic photo upload on mobile is the killer feature for household adoption. Set it up for every family member’s phone.
- Back up the database, config, and data directory separately. Use rsync for incremental data backups rather than full archives.
Related Guides
If you found this useful, these guides continue the journey:
- Docker Compose for Beginners — manage multi-container setups like Nextcloud with ease
- Nginx Proxy Manager Setup — put Nextcloud behind a reverse proxy with HTTPS
- Self-Host Vaultwarden — another essential self-hosted service to pair with Nextcloud
- Build Your First Homelab in 2026 — the complete guide to getting started
- Why Self-Host in 2026? — the case for digital sovereignty and owning your data
- Essential Docker Commands — troubleshoot and manage your containers
RTFM Apparel · printed in the UK

ReadTheManual is run, written and curated by Eric Lonsdale.
Eric has over 20 years of professional experience in IT infrastructure, cloud architecture, and cybersecurity, but started with PCs long before that.
He built his first machine from parts bought off tables at the local college campus, hoping they worked. He learned on BBC Micros and Atari units in the early 90s, and has built almost every PC he’s used between 1995 and now.
From helpdesk to infrastructure architect, Eric has worked across enterprise datacentres, Azure environments, and security operations. He’s managed teams, trained engineers, and spent two decades solving the problems this site teaches you to solve.
ReadTheManual exists because Eric believes the best way to learn IT is to build things, break things, and actually read the manual. Every guide on this site runs on infrastructure he owns and maintains.
Enjoyed this guide?
New articles on Linux, homelab, cloud, and automation every 2 days. No spam, unsubscribe anytime.







