Nextcloud¶
Self-hosted cloud storage, calendar, contacts, and office collaboration with data on ZFS.
Overview¶
Nextcloud provides:
- File Sync - Desktop and mobile clients
- Office - Collaborative document editing
- Calendar/Contacts - CalDAV/CardDAV sync
- Talk - Video calls and chat
- Photos - AI-powered photo management
- External Storage - Mount S3, SMB, WebDAV
- Apps - 400+ integrations
Data Layout¶
| ZFS Dataset | Container Path | Purpose |
|---|---|---|
| tank/nextcloud-data | /var/www/html/data | User files |
| tank/nextcloud-app | /var/www/html | App config |
| tank/db | /var/lib/mysql | Database |
Docker Compose¶
# docker-compose.yml
services:
db:
image: mariadb:11
container_name: nextcloud-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- /mnt/tank/db/nextcloud:/var/lib/mysql
redis:
image: redis:alpine
container_name: nextcloud-redis
restart: unless-stopped
nextcloud:
image: nextcloud:stable
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
NEXTCLOUD_TRUSTED_DOMAINS: ${NEXTCLOUD_DOMAIN}
volumes:
- /mnt/tank/nextcloud-app:/var/www/html
- /mnt/tank/nextcloud-data:/var/www/html/data
ports:
- "8080:80"
Environment File¶
# .env
MYSQL_ROOT_PASSWORD=<secure-password>
MYSQL_PASSWORD=<secure-password>
NEXTCLOUD_DOMAIN=nextcloud.local
Prepare ZFS Datasets¶
# Create datasets if not done
sudo zfs create tank/nextcloud-app
sudo zfs create tank/nextcloud-data
sudo zfs create tank/db/nextcloud
# Set permissions (www-data = UID 33)
sudo chown -R 33:33 /mnt/tank/nextcloud-app
sudo chown -R 33:33 /mnt/tank/nextcloud-data
sudo chown -R 999:999 /mnt/tank/db/nextcloud
Deploy¶
Access at http://server-ip:8080.
Initial Setup¶
- Create admin account
- Configure database (already set via environment)
- Install recommended apps
Maintenance¶
Backup Strategy¶
# Snapshot before upgrade
sudo zfs snapshot tank/nextcloud-data@pre-upgrade
sudo zfs snapshot tank/nextcloud-app@pre-upgrade
sudo zfs snapshot tank/db/nextcloud@pre-upgrade
Update¶
Scan Files¶
After adding files externally:
Reverse Proxy / HTTPS¶
Caddy Configuration¶
Caddy provides automatic HTTPS with Let's Encrypt.
Install Caddy on the host:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy
Configure /etc/caddy/Caddyfile:
nextcloud.example.com {
reverse_proxy localhost:8080
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
}
redir /.well-known/carddav /remote.php/dav 301
redir /.well-known/caldav /remote.php/dav 301
}
Reload Caddy:
Trusted Domains¶
Update Nextcloud to trust the domain:
docker exec -u www-data nextcloud php occ config:system:set \
trusted_domains 0 --value="nextcloud.example.com"
docker exec -u www-data nextcloud php occ config:system:set \
overwrite.cli.url --value="https://nextcloud.example.com"
docker exec -u www-data nextcloud php occ config:system:set \
overwriteprotocol --value="https"
nginx-proxy Alternative¶
Using nginx-proxy with companion for HTTPS:
services:
nginx-proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- certs:/etc/nginx/certs
- html:/usr/share/nginx/html
- vhost:/etc/nginx/vhost.d
acme-companion:
image: nginxproxy/acme-companion
container_name: acme-companion
volumes_from:
- nginx-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- acme:/etc/acme.sh
environment:
DEFAULT_EMAIL: admin@example.com
nextcloud:
# ... existing config ...
environment:
# ... existing vars ...
VIRTUAL_HOST: nextcloud.example.com
LETSENCRYPT_HOST: nextcloud.example.com
Troubleshooting¶
Common occ Commands¶
# Run any occ command
docker exec -u www-data nextcloud php occ <command>
# Check status
docker exec -u www-data nextcloud php occ status
# Maintenance mode
docker exec -u www-data nextcloud php occ maintenance:mode --on
docker exec -u www-data nextcloud php occ maintenance:mode --off
# Repair
docker exec -u www-data nextcloud php occ maintenance:repair
# Database maintenance
docker exec -u www-data nextcloud php occ db:add-missing-indices
docker exec -u www-data nextcloud php occ db:add-missing-columns
docker exec -u www-data nextcloud php occ db:add-missing-primary-keys
Database Issues¶
Connection refused:
# Check database container is running
docker ps | grep nextcloud-db
# Check logs
docker logs nextcloud-db
# Test connection
docker exec nextcloud-db mysql -u nextcloud -p -e "SELECT 1"
Locked database:
# Check for locks
docker exec nextcloud-db mysql -u root -p -e \
"SHOW PROCESSLIST"
# Kill long-running queries if needed
docker exec nextcloud-db mysql -u root -p -e \
"KILL <process_id>"
Corrupted database:
# Check tables
docker exec nextcloud-db mysqlcheck -u root -p --all-databases
# Repair tables
docker exec nextcloud-db mysqlcheck -u root -p --repair nextcloud
File Locking Problems¶
Locked files that won't unlock:
# View locks
docker exec -u www-data nextcloud php occ files:scan --all
# Clear all file locks
docker exec nextcloud-db mysql -u root -p -e \
"DELETE FROM nextcloud.oc_file_locks WHERE 1"
# Restart
docker compose restart nextcloud
Upgrade Failures¶
Upgrade stuck in maintenance mode:
# Disable maintenance mode
docker exec -u www-data nextcloud php occ maintenance:mode --off
# Check version
docker exec -u www-data nextcloud php occ status
# Retry upgrade
docker exec -u www-data nextcloud php occ upgrade
Failed upgrade (need to rollback):
# Stop containers
docker compose down
# Rollback to pre-upgrade snapshot
sudo zfs rollback tank/nextcloud-app@pre-upgrade
sudo zfs rollback tank/nextcloud-data@pre-upgrade
sudo zfs rollback tank/db/nextcloud@pre-upgrade
# Start with previous image version
# Edit docker-compose.yml to pin previous version
docker compose up -d
Performance Issues¶
# Check cron job is running
docker exec -u www-data nextcloud php occ background:cron
# Clear caches
docker exec -u www-data nextcloud php occ maintenance:repair
# Check preview generation
docker exec -u www-data nextcloud php occ preview:generate-all
Restore Procedure¶
From ZFS Snapshot¶
-
Stop services:
-
Identify target snapshot:
-
Rollback all datasets:
-
Restart services:
-
Verify:
From Database Dump¶
-
Stop Nextcloud (keep database running):
-
Restore database:
-
Restart:
-
Run maintenance:
Verifying Restored Data¶
After any restore:
-
Check status:
-
Verify file counts:
-
Test login and file access via web UI
-
Check logs for errors:
Performance Optimization¶
Enable Memory Caching¶
Add APCu for local caching:
docker exec -u www-data nextcloud php occ config:system:set memcache.local --value="\OC\Memcache\APCu"
Redis is already configured in the compose file for distributed caching:
docker exec -u www-data nextcloud php occ config:system:set memcache.distributed --value="\OC\Memcache\Redis"
docker exec -u www-data nextcloud php occ config:system:set redis host --value="redis"
docker exec -u www-data nextcloud php occ config:system:set redis port --value="6379" --type=integer
Enable Locking with Redis¶
docker exec -u www-data nextcloud php occ config:system:set memcache.locking --value="\OC\Memcache\Redis"
Database Optimization¶
Enable database indices:
docker exec -u www-data nextcloud php occ db:add-missing-indices
docker exec -u www-data nextcloud php occ db:add-missing-columns
docker exec -u www-data nextcloud php occ db:add-missing-primary-keys
docker exec -u www-data nextcloud php occ db:convert-filecache-bigint
PHP Configuration¶
Create optimized PHP config:
cat << 'EOF' > php-custom.ini
memory_limit = 512M
upload_max_filesize = 16G
post_max_size = 16G
max_execution_time = 3600
max_input_time = 3600
opcache.enable = 1
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 10000
opcache.memory_consumption = 128
opcache.save_comments = 1
opcache.revalidate_freq = 1
EOF
Mount in docker-compose.yml:
Preview Generation¶
Pre-generate previews for faster browsing:
# One-time generation for all files
docker exec -u www-data nextcloud php occ preview:generate-all
# Configure preview sizes
docker exec -u www-data nextcloud php occ config:system:set preview_max_x --value="2048"
docker exec -u www-data nextcloud php occ config:system:set preview_max_y --value="2048"
Background Jobs (Cron)¶
Configure Cron (Recommended)¶
Use system cron instead of AJAX or Webcron:
Add host cron job:
Or use a dedicated cron container:
services:
cron:
image: nextcloud:stable
container_name: nextcloud-cron
restart: unless-stopped
depends_on:
- db
- redis
volumes:
- /mnt/tank/nextcloud-app:/var/www/html
- /mnt/tank/nextcloud-data:/var/www/html/data
entrypoint: /cron.sh
Verify Cron is Working¶
# Check last cron run
docker exec -u www-data nextcloud php occ background:cron
# Check status
docker exec -u www-data nextcloud php occ status
Essential Apps¶
Install Recommended Apps¶
# Office suite (requires Collabora or OnlyOffice)
docker exec -u www-data nextcloud php occ app:install richdocuments
# Calendar and contacts
docker exec -u www-data nextcloud php occ app:install calendar
docker exec -u www-data nextcloud php occ app:install contacts
# External storage
docker exec -u www-data nextcloud php occ app:install files_external
# Two-factor authentication
docker exec -u www-data nextcloud php occ app:install twofactor_totp
# Photos with AI
docker exec -u www-data nextcloud php occ app:install memories
docker exec -u www-data nextcloud php occ app:install recognize
# Talk (video calls)
docker exec -u www-data nextcloud php occ app:install spreed
# Notes
docker exec -u www-data nextcloud php occ app:install notes
Collabora Online (Office)¶
Add to docker-compose.yml:
services:
collabora:
image: collabora/code:latest
container_name: collabora
restart: unless-stopped
environment:
- domain=nextcloud\\.example\\.com # Escape dots
- username=admin
- password=${COLLABORA_PASSWORD}
- extra_params=--o:ssl.enable=false --o:ssl.termination=true
ports:
- "9980:9980"
cap_add:
- MKNOD
Configure in Nextcloud:
- Admin Settings > Richdocuments
- Set Collabora URL:
http://collabora:9980
External Storage¶
Mount SMB/CIFS Share¶
# Enable external storage app
docker exec -u www-data nextcloud php occ app:enable files_external
# Configure via web UI: Settings > External Storage
Or via occ:
docker exec -u www-data nextcloud php occ files_external:create \
"/SMB Share" \
smb \
password::password \
--config host=192.168.1.100 \
--config share=shared \
--config root=/ \
--config domain=WORKGROUP \
--config user=smbuser \
--config password=smbpass
Mount Local Folder¶
Mount additional host directories in docker-compose.yml:
volumes:
- /mnt/tank/nextcloud-app:/var/www/html
- /mnt/tank/nextcloud-data:/var/www/html/data
- /mnt/tank/media:/external/media:ro # Additional mount
Configure as local external storage:
docker exec -u www-data nextcloud php occ files_external:create \
"/Media" \
local \
null::null \
--config datadir=/external/media
Security Hardening¶
Enable Brute Force Protection¶
docker exec -u www-data nextcloud php occ config:system:set auth.bruteforce.protection.enabled --value=true --type=boolean
Set Secure Headers¶
Add to config.php via occ:
# Force HTTPS
docker exec -u www-data nextcloud php occ config:system:set overwriteprotocol --value="https"
# Secure cookies
docker exec -u www-data nextcloud php occ config:system:set forcessl --value=true --type=boolean
Enable Two-Factor Authentication¶
# Install TOTP app
docker exec -u www-data nextcloud php occ app:install twofactor_totp
# Enforce for all users (optional)
docker exec -u www-data nextcloud php occ twofactorauth:enforce --on
Disable Unused Features¶
# Disable app recommendations
docker exec -u www-data nextcloud php occ config:system:set appstoreenabled --value=false --type=boolean
# Disable check for updates (use docker pull instead)
docker exec -u www-data nextcloud php occ config:system:set updatechecker --value=false --type=boolean
Client Setup¶
Desktop Sync Client¶
Install Nextcloud Desktop on your workstation:
- Linux:
flatpak install flathub com.nextcloud.desktopclient.nextcloud - macOS: Homebrew or DMG
- Windows: MSI installer
Configure: 1. Enter server URL (e.g., https://nextcloud.example.com) 2. Authenticate via browser 3. Select folders to sync
Mobile Apps¶
- Nextcloud Files - iOS/Android
- Nextcloud Talk - Video calls
- DAVx5 - Calendar/Contacts sync on Android
CalDAV/CardDAV URLs¶
For calendar/contacts sync in external apps:
| Service | URL |
|---|---|
| CalDAV | https://nextcloud.example.com/remote.php/dav |
| CardDAV | https://nextcloud.example.com/remote.php/dav |
Monitoring¶
Check System Status¶
# Full status
docker exec -u www-data nextcloud php occ status
# Security scan
docker exec -u www-data nextcloud php occ security:certificates
# App updates
docker exec -u www-data nextcloud php occ app:update --all
Log Analysis¶
# View Nextcloud log
docker exec nextcloud tail -f /var/www/html/data/nextcloud.log
# Parse as JSON
docker exec nextcloud cat /var/www/html/data/nextcloud.log | jq .
Health Check Script¶
#!/bin/bash
# /usr/local/bin/check-nextcloud.sh
STATUS=$(docker exec -u www-data nextcloud php occ status --output=json 2>/dev/null)
if echo "$STATUS" | jq -e '.installed == true and .maintenance == false' > /dev/null; then
echo "Nextcloud: OK"
exit 0
else
echo "Nextcloud: PROBLEM"
echo "$STATUS" | jq .
exit 1
fi
See Also¶
- Docker Setup - Docker installation
- Resource Limits - Container constraints
- ZFS - Dataset management and snapshots
- Tailscale - Remote access