Self-Hosting Nextcloud: Your Own Cloud Storage Platform
Every file you put in Google Drive or Dropbox lives on someone else's infrastructure under someone else's terms. They can change pricing, scan your files, train AI on your documents, or shut down your account with an automated email and no recourse. If you have a homelab, you can run your own cloud storage that you actually control.
Nextcloud is the most mature self-hosted cloud platform available. It started as an ownCloud fork in 2016 and has since grown into a full productivity suite — file sync, calendar, contacts, document editing, video calls, and a plugin ecosystem with hundreds of apps. It's used by the German federal government, the French Ministry of the Interior, and thousands of organizations that take data sovereignty seriously.
But maturity comes with complexity. Nextcloud has more configuration knobs than most self-hosted apps, and the difference between a sluggish install and a fast one comes down to a handful of tuning decisions. This guide covers the deployment that actually works well: Docker Compose with PostgreSQL, Redis caching, and proper PHP tuning.
Hardware Requirements
Nextcloud itself is lightweight, but performance depends heavily on how you configure it.
Minimum (small household, basic file sync):
- 2 CPU cores
- 2 GB RAM
- 20 GB for the application (plus your file storage)
Recommended (comfortable daily use with apps):
- 4+ CPU cores
- 4 GB RAM
- SSD for the database and application, HDD fine for bulk file storage
- Separate storage volume for user data
Storage math: Nextcloud stores files at their original size with no compression. A household of four people each syncing documents, photos, and misc files typically needs 500 GB to 2 TB. If you're replacing Google Drive, check your current usage in Google's storage manager to get a realistic number.
The database is the performance-critical component. Put PostgreSQL on an SSD. Your actual files can live on a larger, slower drive or NAS without affecting the responsiveness of the web interface.
Docker Compose Deployment
Docker Compose is the recommended installation method for homelabs. It isolates Nextcloud and its dependencies, makes updates predictable, and avoids the PHP dependency headaches that plague bare-metal installations.
Create a directory for the deployment:
mkdir -p /opt/nextcloud
cd /opt/nextcloud
Create a .env file:
# /opt/nextcloud/.env
# Database
POSTGRES_USER=nextcloud
POSTGRES_PASSWORD=change-this-to-a-strong-password
POSTGRES_DB=nextcloud
# Nextcloud admin
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=change-this-too
# Domain — set this to your actual domain or IP
NEXTCLOUD_TRUSTED_DOMAINS=cloud.yourdomain.com
# Timezone
TZ=America/New_York
Create docker-compose.yml:
# /opt/nextcloud/docker-compose.yml
services:
nextcloud:
image: nextcloud:29-apache
container_name: nextcloud-app
restart: unless-stopped
ports:
- "8080:80"
volumes:
- nextcloud-app:/var/www/html
- /mnt/data/nextcloud:/var/www/html/data
environment:
POSTGRES_HOST: nextcloud-db
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER}
NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
NEXTCLOUD_TRUSTED_DOMAINS: ${NEXTCLOUD_TRUSTED_DOMAINS}
REDIS_HOST: nextcloud-redis
OVERWRITEPROTOCOL: https
OVERWRITECLIURL: https://${NEXTCLOUD_TRUSTED_DOMAINS}
depends_on:
nextcloud-db:
condition: service_healthy
nextcloud-redis:
condition: service_healthy
nextcloud-db:
image: postgres:16-alpine
container_name: nextcloud-db
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- nextcloud-db-data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}
interval: 10s
timeout: 5s
retries: 5
nextcloud-redis:
image: redis:7-alpine
container_name: nextcloud-redis
restart: unless-stopped
command: redis-server --requirepass redis-secret-password
volumes:
- nextcloud-redis-data:/data
healthcheck:
test: redis-cli -a redis-secret-password ping | grep PONG
interval: 10s
timeout: 5s
retries: 5
nextcloud-cron:
image: nextcloud:29-apache
container_name: nextcloud-cron
restart: unless-stopped
entrypoint: /cron.sh
volumes:
- nextcloud-app:/var/www/html
- /mnt/data/nextcloud:/var/www/html/data
environment:
POSTGRES_HOST: nextcloud-db
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
REDIS_HOST: nextcloud-redis
depends_on:
- nextcloud
volumes:
nextcloud-app:
nextcloud-db-data:
nextcloud-redis-data:
A few things to note about this setup:
- PostgreSQL over MySQL/MariaDB. Both work, but PostgreSQL handles concurrent access better and is the database the Nextcloud team tests against most heavily.
- Redis is included from the start. File locking and caching through Redis makes a massive difference in responsiveness. Don't skip it.
- A separate cron container runs background jobs. Nextcloud needs periodic background tasks for file scanning, cleanup, and app updates. The cron container handles this reliably without configuring cron on the host.
- The data directory is bind-mounted to
/mnt/data/nextcloud. This separates your user files from the application container, making backups and storage management straightforward.
Start everything:
docker compose up -d
Open http://your-server:8080 in your browser. The first load takes a minute while the database is initialized. Log in with the admin credentials from your .env file.
Post-Installation Configuration
After the initial setup, there are several configuration changes that significantly improve the experience.
Configure Redis for File Locking and Caching
The Docker image auto-configures basic Redis caching if REDIS_HOST is set, but you should verify and extend the configuration. Run:
docker exec -u www-data nextcloud-app php occ config:system:set \
redis host --value="nextcloud-redis"
docker exec -u www-data nextcloud-app php occ config:system:set \
redis port --value="6379" --type=integer
docker exec -u www-data nextcloud-app php occ config:system:set \
redis password --value="redis-secret-password"
docker exec -u www-data nextcloud-app php occ config:system:set \
memcache.local --value="\\OC\\Memcache\\APCu"
docker exec -u www-data nextcloud-app php occ config:system:set \
memcache.distributed --value="\\OC\\Memcache\\Redis"
docker exec -u www-data nextcloud-app php occ config:system:set \
memcache.locking --value="\\OC\\Memcache\\Redis"
This sets up a three-tier caching strategy: APCu for local (per-process) caching, Redis for distributed caching across requests, and Redis for file locking. The result is noticeably snappier page loads and file operations.
Set the Background Job Mode
Verify that cron is being used for background jobs instead of AJAX (the default):
docker exec -u www-data nextcloud-app php occ background:cron
AJAX-based background jobs only run when someone visits the web interface. Cron runs them on schedule regardless of user activity. This matters for file scanning, notification delivery, and app updates.
Configure Trusted Domains and Proxies
If you're behind a reverse proxy (and you should be for HTTPS), configure the trusted proxy settings:
docker exec -u www-data nextcloud-app php occ config:system:set \
trusted_proxies 0 --value="172.16.0.0/12"
docker exec -u www-data nextcloud-app php occ config:system:set \
overwriteprotocol --value="https"
Adjust the trusted proxy CIDR to match your Docker network. Without this, Nextcloud will show security warnings and mixed content issues behind a reverse proxy.
Set the Default Phone Region
Nextcloud will nag you about this in the admin panel:
docker exec -u www-data nextcloud-app php occ config:system:set \
default_phone_region --value="US"
Performance Tuning
Out of the box, Nextcloud feels sluggish. This is the number one complaint about the platform, and it's almost entirely fixable with proper configuration.
PHP OPcache
OPcache caches compiled PHP bytecode so that scripts don't need to be parsed and compiled on every request. The default settings are conservative. Create a custom PHP configuration:
docker exec nextcloud-app bash -c 'cat > /usr/local/etc/php/conf.d/opcache-recommended.ini << EOF
opcache.enable=1
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=10000
opcache.memory_consumption=256
opcache.save_comments=1
opcache.revalidate_freq=60
EOF'
Then restart the container:
docker compose restart nextcloud
The key settings: memory_consumption=256 gives OPcache 256 MB (the default is 128 MB, which is too small for Nextcloud with apps). max_accelerated_files=10000 covers all of Nextcloud's PHP files. revalidate_freq=60 means OPcache checks for file changes every 60 seconds instead of every request.
PHP Memory Limit
The default PHP memory limit of 128 MB is insufficient for Nextcloud, especially when generating file previews or processing uploads:
docker exec nextcloud-app bash -c 'cat > /usr/local/etc/php/conf.d/memory-limit.ini << EOF
memory_limit=1024M
upload_max_filesize=16G
post_max_size=16G
max_execution_time=3600
max_input_time=3600
EOF'
Restart again after this change. The 16 GB upload limit accommodates large video files. Adjust max_execution_time based on how large your uploads are and how fast your storage is.
Database Indexing
Run the database index optimization after the initial setup and periodically after updates:
docker exec -u www-data nextcloud-app php occ db:add-missing-indices
docker exec -u www-data nextcloud-app php occ db:add-missing-columns
docker exec -u www-data nextcloud-app php occ db:add-missing-primary-keys
These commands add database indices that Nextcloud expects but that might not have been created during installation or migration. Missing indices cause slow queries that make the entire interface feel laggy.
Preview Generation
Nextcloud generates image previews on-the-fly by default, which is slow when browsing folders with many images. The Preview Generator app creates them in advance:
# Install the preview generator app
docker exec -u www-data nextcloud-app php occ app:install previewgenerator
# Configure preview sizes
docker exec -u www-data nextcloud-app php occ config:app:set \
previewgenerator squareSizes --value="32 256"
docker exec -u www-data nextcloud-app php occ config:app:set \
previewgenerator widthSizes --value="256 384"
docker exec -u www-data nextcloud-app php occ config:app:set \
previewgenerator heightSizes --value="256"
# Generate previews for all existing files
docker exec -u www-data nextcloud-app php occ preview:generate-all
Add preview generation for new files to the cron schedule by running preview:pre-generate as a periodic background task. The cron container handles this automatically if the app is installed.
Security Hardening
Enforce HTTPS
Never run Nextcloud over plain HTTP in production. Use a reverse proxy with TLS termination. Caddy is the easiest option since it handles Let's Encrypt certificates automatically:
# /etc/caddy/Caddyfile
cloud.yourdomain.com {
reverse_proxy localhost:8080
header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
}
Brute Force Protection
Nextcloud has built-in brute force protection that throttles failed login attempts. Verify it's enabled:
docker exec -u www-data nextcloud-app php occ config:system:get \
auth.bruteforce.protection.enabled
It should return true (this is the default). The throttle starts after repeated failed attempts from the same IP and progressively increases the delay.
Two-Factor Authentication
Enable TOTP two-factor authentication for all accounts:
docker exec -u www-data nextcloud-app php occ app:install twofactor_totp
Then each user enables it in their personal security settings. For admin accounts, enforce it:
docker exec -u www-data nextcloud-app php occ twofactorauth:enforce --on
File Access Control
The File Access Control app lets you create rules that block specific operations. Common use cases:
- Prevent sharing files externally based on tags or file types
- Block uploads of certain file extensions
- Restrict access to specific folders by group membership
docker exec -u www-data nextcloud-app php occ app:install files_accesscontrol
Configure rules in the admin panel under Administration > Flow.
Security Headers
If you're using Caddy or Nginx as your reverse proxy, add security headers:
# Additional Caddy headers
cloud.yourdomain.com {
reverse_proxy localhost:8080
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "no-referrer"
X-Robots-Tag "noindex, nofollow"
Permissions-Policy "interest-cohort=()"
}
}
Regular Security Scans
Nextcloud provides an online security scanner at scan.nextcloud.com. Run it periodically against your instance to check for known vulnerabilities, misconfiguration, and missing headers.
Storage Backend Options
Local Filesystem
The simplest option. Mount a local drive or partition and point Nextcloud's data directory at it. This is what the Docker Compose setup above uses with the bind mount to /mnt/data/nextcloud.
Network Storage (NFS/SMB)
For NAS-backed storage, mount the network share on the host and bind-mount it into the container:
# /etc/fstab entry for an NFS share
nas.local:/volume1/nextcloud /mnt/nas/nextcloud nfs defaults,_netdev 0 0
Then update the Docker Compose volume:
volumes:
- nextcloud-app:/var/www/html
- /mnt/nas/nextcloud:/var/www/html/data
NFS generally performs better than SMB for Linux-to-Linux file serving. If your NAS runs TrueNAS, Unraid, or similar, NFS is the right choice.
External Storage App
Nextcloud's External Storage app lets you mount remote storage directly within the Nextcloud file browser without moving files to local storage:
docker exec -u www-data nextcloud-app php occ app:install files_external
Supported backends include:
- S3-compatible storage (AWS S3, MinIO, Backblaze B2, Wasabi)
- SFTP/FTP servers
- SMB/CIFS shares
- WebDAV endpoints
- OpenStack Swift object storage
This is useful for mounting existing storage that you don't want to migrate. Files appear in the user's Nextcloud but are stored on the external backend.
For S3 primary storage (all user files stored in S3 instead of local disk), configure it in config.php:
'objectstore' => [
'class' => '\\OC\\Files\\ObjectStore\\S3',
'arguments' => [
'bucket' => 'nextcloud-data',
'hostname' => 'minio.local',
'port' => 9000,
'use_ssl' => false,
'use_path_style' => true,
'autocreate' => true,
'key' => 'minio-access-key',
'secret' => 'minio-secret-key',
'region' => 'us-east-1',
],
],
S3 primary storage is a one-way decision. You can't switch back to local storage without a full data migration. It works well with MinIO for homelabs that want to separate compute from storage.
Backup Strategy
What to Back Up
- The data directory (
/mnt/data/nextcloud) -- Your users' files. This is the irreplaceable data. - The database -- Contains file metadata, user accounts, shares, calendar entries, contacts, and app configurations.
- The config directory -- Contains
config.phpwith your instance's unique configuration. Stored inside thenextcloud-appvolume.
Maintenance Mode
Always put Nextcloud in maintenance mode before backing up to ensure data consistency:
docker exec -u www-data nextcloud-app php occ maintenance:mode --on
Re-enable after backup:
docker exec -u www-data nextcloud-app php occ maintenance:mode --off
Database Backup
docker exec nextcloud-db pg_dump -U nextcloud nextcloud \
| gzip > /path/to/backups/nextcloud-db-$(date +%Y%m%d).sql.gz
Automated Backup Script
#!/bin/bash
# /opt/nextcloud/backup.sh
set -euo pipefail
BACKUP_DIR="/mnt/nas/backups/nextcloud/$(date +%Y-%m-%d)"
mkdir -p "$BACKUP_DIR"
echo "Enabling maintenance mode..."
docker exec -u www-data nextcloud-app php occ maintenance:mode --on
echo "Backing up database..."
docker exec nextcloud-db pg_dump -U nextcloud -Fc nextcloud \
> "$BACKUP_DIR/database.dump"
echo "Backing up config..."
docker cp nextcloud-app:/var/www/html/config/config.php "$BACKUP_DIR/config.php"
echo "Backing up data directory..."
rsync -a --delete /mnt/data/nextcloud/ "$BACKUP_DIR/data/"
echo "Disabling maintenance mode..."
docker exec -u www-data nextcloud-app php occ maintenance:mode --off
# Keep last 7 daily backups
find /mnt/nas/backups/nextcloud/ -maxdepth 1 -type d -mtime +7 -exec rm -rf {} \;
echo "Nextcloud backup completed: $BACKUP_DIR"
chmod +x /opt/nextcloud/backup.sh
# Schedule daily at 2 AM
echo "0 2 * * * root /opt/nextcloud/backup.sh >> /var/log/nextcloud-backup.log 2>&1" \
| sudo tee /etc/cron.d/nextcloud-backup
For offsite protection, pipe the backup directory into your existing borg or restic workflow, or sync it to cloud storage with rclone.
Desktop and Mobile Sync Clients
The sync clients are how most people interact with Nextcloud day-to-day. They keep a local folder in sync with the server, similar to Dropbox or Google Drive.
Desktop Client
The Nextcloud desktop client is available for Linux, macOS, and Windows.
# Fedora
sudo dnf install nextcloud-client
# Ubuntu/Debian
sudo apt install nextcloud-desktop
# macOS (Homebrew)
brew install --cask nextcloud
# Windows — download from nextcloud.com/install/#install-clients
Configuration after installation:
- Enter your server URL (e.g.,
https://cloud.yourdomain.com) - Authenticate through the browser-based login flow
- Choose which folders to sync and where to store them locally
- Optionally enable virtual files (files appear in your file manager but are only downloaded on demand)
Virtual files are worth enabling if you have a large Nextcloud library but limited local disk space. On Linux, this uses the FUSE-based virtual filesystem. On Windows, it integrates with the native cloud files API. On macOS, support is still maturing.
Mobile Apps
Official apps are available for iOS (App Store) and Android (Play Store / F-Droid).
The mobile apps provide:
- Auto-upload for photos and videos, similar to Google Photos backup
- Offline files that you can mark for download
- Share links that you can create and send directly from your phone
- Notifications for shares, comments, and activity
Configure auto-upload in the app settings. Set it to Wi-Fi only and choose which folders to monitor (camera roll, screenshots, downloads). The initial sync of an existing photo library takes time -- run it overnight.
WebDAV Access
Nextcloud exposes all files over WebDAV, which means any WebDAV client can access your files:
https://cloud.yourdomain.com/remote.php/dav/files/USERNAME/
This is useful for mounting Nextcloud as a network drive in your OS file manager, connecting from third-party apps, or scripting file operations with curl or rclone:
# Mount Nextcloud via rclone
rclone config
# Choose "webdav" as the type
# URL: https://cloud.yourdomain.com/remote.php/dav/files/USERNAME/
# Vendor: nextcloud
# Then mount it
rclone mount nextcloud: /mnt/nextcloud --vfs-cache-mode full
Useful Apps and Integrations
Nextcloud's app ecosystem is one of its biggest strengths. Here are the apps worth installing beyond the defaults.
Nextcloud Office (Collabora or OnlyOffice)
Collaborative document editing, like Google Docs but self-hosted. You have two options:
Collabora Online (the Nextcloud-recommended option):
# Add to docker-compose.yml
collabora:
image: collabora/code:latest
container_name: nextcloud-collabora
restart: unless-stopped
ports:
- "9980:9980"
environment:
aliasgroup1: https://cloud.yourdomain.com:443
extra_params: "--o:ssl.enable=false --o:ssl.termination=true"
cap_add:
- MKNOD
Install the "Nextcloud Office" app from the app store and point it at your Collabora container.
OnlyOffice is the alternative. It generally has better Microsoft Office compatibility but uses more resources. Choose one -- running both is not supported.
Calendar and Contacts
docker exec -u www-data nextcloud-app php occ app:install calendar
docker exec -u www-data nextcloud-app php occ app:install contacts
These sync via CalDAV and CardDAV. You can replace Google Calendar and Google Contacts entirely. Most phone calendar and contact apps support CalDAV/CardDAV natively -- on iOS it's built in, on Android use DAVx5.
Notes and Tasks
docker exec -u www-data nextcloud-app php occ app:install notes
docker exec -u www-data nextcloud-app php occ app:install tasks
Notes syncs with the Nextcloud Notes mobile app. Tasks syncs via CalDAV with any task app that supports it.
Nextcloud Talk
Video calls and chat, self-hosted. Works surprisingly well for small group calls. For larger meetings or better performance, add a TURN server:
docker exec -u www-data nextcloud-app php occ app:install spreed
Bookmarks, Deck, and Maps
- Bookmarks: Self-hosted bookmark manager with browser extensions
- Deck: Kanban board for project management (like a simple Trello)
- Maps: View your geotagged photos on a map
Install any of these from the app store in the admin panel.
Updating Nextcloud
Nextcloud updates require care. Major version upgrades (e.g., 28 to 29) can only be done one major version at a time -- you cannot skip versions.
Minor Updates (within a major version)
cd /opt/nextcloud
docker compose pull
docker compose up -d
After the containers restart, run the upgrade command:
docker exec -u www-data nextcloud-app php occ upgrade
Major Version Upgrades
- Back up everything first (database, data, config)
- Update the image tag in
docker-compose.ymlto the next major version - Pull and restart
- Run the upgrade command
- Check for app compatibility -- some apps may need updates
# Check which apps need updates after a major upgrade
docker exec -u www-data nextcloud-app php occ app:update --all
Read the release notes before upgrading. Nextcloud occasionally deprecates features, changes PHP requirements, or requires manual migration steps. The admin panel also shows warnings about pending upgrades and compatibility issues.
Pinning a Version
In production, pin to a specific version rather than using latest:
image: nextcloud:29.0.8-apache
This prevents unintended upgrades when you run docker compose pull.
Monitoring and Troubleshooting
Admin Panel Warnings
The Settings > Administration > Overview page shows warnings about misconfigurations. After initial setup, work through every warning until the page is clean. Common ones:
- Missing database indices (run
occ db:add-missing-indices) - Background jobs not running (switch to cron)
- Missing OPcache settings
- Reverse proxy configuration issues
Nextcloud Log
docker exec -u www-data nextcloud-app php occ log:tail
Or read the log file directly:
docker exec nextcloud-app cat /var/www/html/data/nextcloud.log | tail -50
Set the log level to reduce noise in production:
docker exec -u www-data nextcloud-app php occ config:system:set \
loglevel --value=2 --type=integer
Log levels: 0 = Debug, 1 = Info, 2 = Warning, 3 = Error, 4 = Fatal.
System Check
docker exec -u www-data nextcloud-app php occ status
docker exec -u www-data nextcloud-app php occ check
Stuck File Locks
If files become locked and refuse to unlock (this happens occasionally after crashes or failed syncs):
docker exec -u www-data nextcloud-app php occ maintenance:mode --on
docker exec nextcloud-db psql -U nextcloud -d nextcloud \
-c "DELETE FROM oc_file_locks WHERE 1=1;"
docker exec -u www-data nextcloud-app php occ maintenance:mode --off
Tips and Gotchas
Don't install every app. The app store is tempting, but every installed app adds PHP overhead, increases memory usage, and can slow down page loads. Install what you'll actually use and remove the rest.
Set up email early. Nextcloud uses email for share notifications, password resets, and activity summaries. Configure an SMTP server in Settings > Administration > Basic settings. Any SMTP provider works -- Fastmail, Gmail app passwords, or a self-hosted mail server.
External storage is slower than local. Mounting S3 or SMB as external storage works, but file listing and browsing is noticeably slower than local filesystem storage. Use it for archival data, not your primary working files.
The sync client occasionally conflicts. When the same file is edited on two devices before a sync completes, the client creates a conflict file rather than silently overwriting. This is the right behavior, but you need to resolve conflicts manually. Check the sync client's activity log periodically.
Large file uploads need tuning. If uploads fail for files over a few hundred megabytes, check your reverse proxy's timeout and body size settings. Nginx needs client_max_body_size set. Caddy handles large uploads without configuration changes.
Nextcloud is not a backup solution. Sync is not backup. If you delete a file on one device, the sync client deletes it everywhere. Nextcloud has a trash bin (30 days by default) and file versioning, but these don't replace proper backups. Back up your Nextcloud data to a separate location.
Nextcloud requires more initial configuration than most self-hosted apps, but once it's tuned properly, it's a reliable platform that can genuinely replace Google Workspace for a household or small team. The combination of file sync, calendar, contacts, and document editing in a single self-hosted platform is hard to match. Take the time to get the performance tuning right, set up proper backups, and it will serve you well.