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.
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).
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-COMMITTEDis required by Nextcloud for MariaDB and MySQL.- Setting all of
MYSQL_DATABASE,MYSQL_USER,MYSQL_PASSWORDandMYSQL_HOSTconfigures the database automatically, so the installer won't ask for it. REDIS_HOSTmakes the image configure Redis for distributed caching and file locking. The image also enables APCu as the local cache, so you don't editconfig.phpfor either.NEXTCLOUD_TRUSTED_DOMAINSis your public hostname.OVERWRITEPROTOCOL=httpstells Nextcloud it is served over HTTPS by the proxy, so it generateshttps://links.- Everything Nextcloud stores outside the database, including user files in
data/andconfig/config.php, lives in thenextcloudvolume. 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
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.
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.
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.



