Self-Host Nextcloud on Raspberry Pi (Complete Guide)
Set up your own cloud storage with Nextcloud on Raspberry Pi. Covers Docker Compose, MariaDB, SSL with Caddy, and mobile sync.
Introduction
Nextcloud has become the go-to solution for privacy-conscious users who want to reclaim control over their personal cloud storage. Unlike commercial services like Google Drive or Dropbox, Nextcloud runs on infrastructure you control—and a Raspberry Pi is a surprisingly capable platform for this.
This guide walks you through setting up a complete Nextcloud instance on a Raspberry Pi, with proper database management, HTTPS encryption, and optimization for limited hardware. Whether you're building a homelab, storing family photos, or syncing documents across devices, you'll have a secure, self-hosted alternative to commercial cloud providers.
Why Self-Host Nextcloud?
Privacy: Your data stays on your hardware. No corporate tracking, no data mining for ad targeting.
Cost: A Raspberry Pi 4/5 costs $50–75 upfront. Monthly recurring fees? Zero.
Control: You decide backup strategies, retention policies, and who accesses what.
Flexibility: Add plugins, customize workflows, and integrate with your other homelab services.
The trade-off is time investment. You're responsible for updates, backups, and troubleshooting. But the result is worth it: a reliable, offline-capable cloud solution that actually belongs to you.
Prerequisites
Before starting, ensure you have:
- Raspberry Pi 4 (8GB RAM) or Pi 5: Pi 3 is too slow; even a Pi 4 4GB struggles with concurrent users. I recommend 8GB minimum.
- SSD storage (not SD card): A 256GB USB 3 SSD (or SATA over USB adapter) is essential. SD cards are too slow for database operations and degrade quickly under heavy I/O.
- Stable power supply: The official Raspberry Pi 27W USB-C PSU or equivalent.
- Stable network: Wired Ethernet strongly preferred over Wi-Fi. Any modern router works.
- Docker and Docker Compose installed: Should already be present on Raspberry Pi OS (Bullseye/Bookworm).
- A domain name (optional but recommended): For HTTPS and remote access. A free domain from Duck DNS works for testing.
- Basic Linux comfort: Editing files, navigating directories, understanding environment variables.
Step 1 — Prepare Your Raspberry Pi and Storage
Start by ensuring your Pi is running the latest Raspberry Pi OS. SSH in and update:
sudo apt update && sudo apt upgrade -y
sudo apt install -y docker.io docker-compose curl wget htop
sudo usermod -aG docker $USER
newgrp docker
Log out and back in so the Docker group change takes effect:
# Test Docker access
docker ps
Now, connect your SSD and identify it:
lsblk
Format it if needed (warning: destructive):
sudo mkfs.ext4 /dev/sda1
sudo mkdir -p /mnt/nextcloud
sudo mount /dev/sda1 /mnt/nextcloud
sudo chown -R $USER:$USER /mnt/nextcloud
Add it to /etc/fstab for automatic mounting on reboot:
sudo blkid /dev/sda1
# Copy the UUID output
sudo nano /etc/fstab
Add this line (replace UUID with your actual UUID):
UUID=12345678-1234-1234-1234-123456789abc /mnt/nextcloud ext4 defaults,noatime 0 0
Save, exit, and test:
sudo mount -a
df -h
Step 2 — Create Docker Compose Configuration
Create a directory structure for Nextcloud:
mkdir -p /mnt/nextcloud/data /mnt/nextcloud/db /mnt/nextcloud/redis /mnt/nextcloud/config
cd /mnt/nextcloud
Create docker-compose.yml:
nano docker-compose.yml
Paste this complete configuration:
version: '3.8'
# Define external volumes for persistence across container restarts
volumes:
nextcloud_data:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/nextcloud/data
nextcloud_db:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/nextcloud/db
nextcloud_redis:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/nextcloud/redis
nextcloud_config:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/nextcloud/config
services:
# MariaDB database service
# MariaDB is a MySQL fork that's lighter on resources than PostgreSQL
# We use version 10.11 (latest stable) which supports ARM64
db:
image: mariadb:10.11-alpine
container_name: nextcloud-db
restart: unless-stopped
environment:
# Root password for MariaDB (use a strong, unique password)
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-SecureRootPass123!}
# Database name to create on initialization
MYSQL_DATABASE: nextcloud
# Database user (not root) for Nextcloud to use
MYSQL_USER: nextcloud
# Password for the nextcloud database user
MYSQL_PASSWORD: ${DB_NEXTCLOUD_PASSWORD:-NextcloudPass456!}
# MariaDB-specific: max allowed packet size (increase for large files)
MYSQL_MAX_ALLOWED_PACKET: 256M
# Timezone (should match host timezone)
TZ: Etc/UTC
volumes:
# Database files persist on the SSD
- nextcloud_db:/var/lib/mysql
command: --transaction-isolation=READ-COMMITTED --binlog-format=mixed --max_connections=512 --slow-query-log=1 --log-bin=/var/lib/mysql/mysql-bin
networks:
- nextcloud
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-u", "root", "-p${DB_ROOT_PASSWORD:-SecureRootPass123!}"]
interval: 10s
timeout: 5s
retries: 5
# Redis cache service (optional but highly recommended)
# Redis significantly reduces database load and speeds up Nextcloud
redis:
image: redis:7-alpine
container_name: nextcloud-redis
restart: unless-stopped
volumes:
- nextcloud_redis:/data
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-RedisSecure789!}
networks:
- nextcloud
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 10s
timeout: 5s
retries: 5
# Nextcloud application service
nextcloud:
image: nextcloud:28-apache
container_name: nextcloud-app
restart: unless-stopped
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
environment:
# Database connection
MYSQL_HOST: db
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: ${DB_NEXTCLOUD_PASSWORD:-NextcloudPass456!}
# Nextcloud admin credentials (set during first install)
NEXTCLOUD_ADMIN_USER: ${NC_ADMIN_USER:-admin}
NEXTCLOUD_ADMIN_PASSWORD: ${NC_ADMIN_PASSWORD:-ChangeMePlease123!}
# Trusted domains (your server hostname/IP)
NEXTCLOUD_TRUSTED_DOMAINS: ${NC_TRUSTED_DOMAINS:-localhost 192.168.1.100 nextcloud.example.com}
# Overwrite protocol for reverse proxy HTTPS termination
OVERWRITEPROTOCOL: https
# Overwrite hostname (set to your domain)
OVERWRITEHOST: ${NC_OVERWRITE_HOST:-nextcloud.example.com}
# Timezone
TZ: Etc/UTC
# PHP memory limit (increase if you have RAM available)
PHP_MEMORY_LIMIT: 768M
# PHP max upload file size
PHP_UPLOAD_LIMIT: 100G
volumes:
# Nextcloud data directory (all user files stored here)
- nextcloud_data:/var/www/html/data
# Nextcloud config directory (settings, installed apps)
- nextcloud_config:/var/www/html/config
ports:
# Internal port 80 (HTTP) for reverse proxy to hit
- "8080:80"
networks:
- nextcloud
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/status.php"]
interval: 30s
timeout: 10s
retries: 3
networks:
nextcloud:
driver: bridge
Create a .env file to store sensitive variables:
nano .env
DB_ROOT_PASSWORD=YourStrongRootPassword123!
DB_NEXTCLOUD_PASSWORD=YourStrongNCPassword456!
REDIS_PASSWORD=YourStrongRedisPassword789!
NC_ADMIN_USER=admin
NC_ADMIN_PASSWORD=YourStrongAdminPassword000!
NC_TRUSTED_DOMAINS=localhost 192.168.1.100 nextcloud.example.com
NC_OVERWRITE_HOST=nextcloud.example.com
Important security notes:
- Use strong, unique passwords (20+ characters, mix of upper/lower/numbers/symbols)
- Never commit
.envto version control - Change
NC_TRUSTED_DOMAINSandNC_OVERWRITE_HOSTto your actual domain - Rotate these passwords regularly in production
Step 3 — Start the Containers
From /mnt/nextcloud, start the stack:
docker-compose up -d
Check that all containers are running:
docker-compose ps
You should see three containers (db, redis, nextcloud) all with status "Up". If any show "Exited", check logs:
docker-compose logs -f nextcloud
Wait 30–60 seconds for Nextcloud to complete initialization. You'll see logs like "Creating database schema... done" once it's ready.
Step 4 — Set Up HTTPS with Caddy (Recommended)
For a production setup, you need HTTPS. Caddy is the easiest modern reverse proxy.
Create docker-compose.caddy.yml:
nano docker-compose.caddy.yml
version: '3.8'
services:
caddy:
image: caddy:latest
container_name: caddy-proxy
restart: unless-stopped
ports:
# HTTP (for Let's Encrypt validation)
- "80:80"
# HTTPS
- "443:443"
volumes:
# Caddyfile configuration
- ./Caddyfile:/etc/caddy/Caddyfile
# Persistent storage for SSL certificates
- caddy_data:/data
- caddy_config:/config
networks:
- nextcloud
environment:
TZ: Etc/UTC
volumes:
caddy_data:
caddy_config:
networks:
nextcloud:
external: true
Create a Caddyfile:
nano Caddyfile
nextcloud.example.com {
# Reverse proxy to the Nextcloud container
reverse_proxy nextcloud-app:80 {
# Preserve original host header
header_up Host {upstream_hostport}
# Pass client IP through X-Forwarded-For
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto https
}
# Redirect caldav/carddav requests to proper endpoints
redir /.well-known/carddav /remote.php/dav 308
redir /.well-known/caldav /remote.php/dav 308
}
Replace nextcloud.example.com with your actual domain.
Start Caddy:
docker-compose -f docker-compose.caddy.yml up -d
Caddy will automatically provision a Let's Encrypt certificate. Your Nextcloud is now accessible at https://nextcloud.example.com.
For local/private networks (no Let's Encrypt):
Use self-signed certificates instead. Create a self-signed cert:
mkdir -p ./certs
openssl req -x509 -newkey rsa:4096 -keyout ./certs/nextcloud.key -out ./certs/nextcloud.crt -days 365 -nodes -subj "/CN=nextcloud.local"
Update your Caddyfile:
nextcloud.local {
tls ./certs/nextcloud.crt ./certs/nextcloud.key
reverse_proxy nextcloud-app:80
}
Step 5 — Initial Nextcloud Setup
Open https://nextcloud.example.com in your browser. You should see the Nextcloud setup wizard. The admin credentials come from your .env file.
Key settings to configure:
- Data folder: Should already be set to
/var/www/html/data. Leave as-is. - Database: Leave as-is (MariaDB detected automatically).
- Click "Finish setup" and wait 2–3 minutes for initialization.
Once loaded, log in with your admin credentials.
Initial Configuration Tasks
Go to Settings → Administration → Overview and configure:
- Background jobs: Set to "Cron" (Settings → Administration → Cron) for reliable scheduled tasks:
# Add to your Pi's crontab
crontab -e
# Add this line (runs every 5 minutes):
*/5 * * * * docker exec nextcloud-app php -f cron.php
Enable Redis caching: Settings → Administration → System
- You should see a green checkmark next to "Memory caching" if Redis is detected.
Increase PHP limits if needed (editing the docker-compose.yml and restarting).
Enable SSL verification: Settings → Administration → System → Enable SSL certification verification (unless using self-signed certificates).
Install useful apps: Settings → Apps
- Files: Already installed
- Deck: Kanban board
- Tasks: Task management
- Calendar: Calendar sync
- Contacts: Contact management
- Notes: Simple note-taking
Step 6 — Install Desktop and Mobile Sync Clients
Desktop Client (Linux/Mac/Windows)
Download from nextcloud.com/install:
# Ubuntu/Debian
sudo apt install nextcloud-client
Launch the client, enter your server URL (https://nextcloud.example.com), and log in. Select folders to sync.
Mobile Apps
iOS: Download "Nextcloud" from App Store Android: Download from Google Play Store or F-Droid
Configure with your server URL and credentials. Enable automatic photo/video backup in settings.
Performance Tuning
Even a Pi 4 can handle 5–10 concurrent users with proper tuning.
PHP Configuration
Increase PHP memory and execution time in your docker-compose.yml:
nextcloud:
environment:
PHP_MEMORY_LIMIT: 1024M
PHP_MAX_EXECUTION_TIME: 3600
Restart:
docker-compose down && docker-compose up -d
Redis Optimization
Redis is already configured for persistence. Monitor hits/misses:
docker exec nextcloud-redis redis-cli -a ${REDIS_PASSWORD} INFO stats
Database Optimization
MariaDB is optimized in the Caddyfile. For further tuning, create a custom my.cnf:
cat > ./my.cnf << 'EOF'
[mysqld]
max_connections = 512
innodb_buffer_pool_size = 256M
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
EOF
Update docker-compose.yml to mount it:
db:
volumes:
- ./my.cnf:/etc/mysql/conf.d/my.cnf
Cron Jobs
Set background jobs to cron instead of "webcron" (more reliable):
# Edit your crontab
crontab -e
Add:
*/5 * * * * docker exec nextcloud-app php -f cron.php
This runs the Nextcloud background job every 5 minutes, handling maintenance tasks without requiring web access.
External Storage
Add external USB drives or NAS mounts to Nextcloud.
USB Drive
Connect the drive and mount it:
mkdir -p /mnt/nextcloud/external/usb
sudo mount /dev/sdb1 /mnt/nextcloud/external/usb
sudo chown -R 33:33 /mnt/nextcloud/external/usb
Update docker-compose.yml:
nextcloud:
volumes:
- /mnt/nextcloud/external/usb:/var/www/html/external_storage/usb
In Nextcloud, go to Settings → Admin → External storages → Add storage.
NAS Mount (NFS)
Mount your NAS:
sudo apt install nfs-common
sudo mount -t nfs 192.168.1.50:/share/nextcloud /mnt/nextcloud/external/nas
Then add to External storages in Nextcloud as above.
Backup Strategy
A backup strategy is crucial. Here's a simple automated approach:
Backup Script
Create /home/pi/nextcloud-backup.sh:
#!/bin/bash
BACKUP_DIR="/mnt/backup"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/nextcloud_$DATE.tar.gz"
# Stop Nextcloud for consistent backup
docker-compose -f /mnt/nextcloud/docker-compose.yml down
# Backup data, config, and database
tar -czf "$BACKUP_FILE" \
/mnt/nextcloud/data \
/mnt/nextcloud/config \
/mnt/nextcloud/db
# Start Nextcloud
docker-compose -f /mnt/nextcloud/docker-compose.yml up -d
# Keep only last 7 days of backups
find "$BACKUP_DIR" -name "nextcloud_*.tar.gz" -mtime +7 -delete
echo "Backup completed: $BACKUP_FILE"
Make executable:
chmod +x /home/pi/nextcloud-backup.sh
Cron Schedule
Schedule daily backups:
crontab -e
Add:
0 2 * * * /home/pi/nextcloud-backup.sh
This runs every day at 2 AM.
Offsite Backup
Use rclone to sync backups to cloud storage (B2, Google Drive, etc.):
sudo apt install rclone
rclone config
# Follow prompts to set up a remote
Add to your backup script:
rclone sync $BACKUP_DIR b2:my-backups --delete-during
Troubleshooting
"Database connection error" on startup
Symptom: Nextcloud won't load, shows database error.
Fix: Wait longer for MariaDB to start (it takes 30–60s on Pi). Check logs:
docker-compose logs db
If it crashes, ensure your SSD has space:
df -h /mnt/nextcloud
"Too many connections" error
Symptom: Database connection pool exhausted.
Fix: Increase max_connections in docker-compose.yml:
db:
command: --max_connections=1024
Or install a connection pool like ProxySQL.
Nextcloud extremely slow (>10s page loads)
Symptom: UI is unresponsive.
Fix:
- Check if Redis is running:
docker-compose ps redis
- Check Pi resource usage:
htop
If RAM is full, increase swap or upgrade to Pi 5 with 8GB RAM.
- Check database slow query log:
docker exec nextcloud-db tail -f /var/log/mysql/slow.log
- Restart everything:
docker-compose down && docker-compose up -d
Mobile client won't sync
Symptom: Files aren't syncing to phone.
Fix:
- Check that client can reach server (test in browser first).
- Verify SSL certificate is valid (check browser warning).
- Log out of mobile app and log back in.
- On iOS: Check Settings → Nextcloud → permissions.
- Check Nextcloud logs:
docker-compose logs nextcloud | grep -i error
Certificate validation errors (desktop/mobile clients)
Symptom: "Unable to verify certificate" on client connections.
Fix: If using self-signed certs, add the certificate to your device's trust store, or in desktop client, go to Settings → Security → "Ignore certificate errors" (not for production).
For production, always use a valid Let's Encrypt certificate via Caddy.
Summary
You now have a fully functional, encrypted Nextcloud instance on a Raspberry Pi. Here's what you've built:
✅ MariaDB database with optimized settings for limited hardware ✅ Redis caching to reduce database load ✅ Nextcloud application with proper environment configuration ✅ HTTPS encryption via Caddy with automatic Let's Encrypt certificates ✅ Desktop and mobile sync clients for file access anywhere ✅ Performance tuning for responsive operation on Pi hardware ✅ External storage support for USB drives and NAS ✅ Automated backups with offsite replication ✅ Troubleshooting knowledge for common issues
The setup is resilient—it survives power failures and restarts automatically. From here, you can:
- Add more storage by connecting additional drives
- Install Nextcloud plugins for calendars, contacts, and tasks
- Set up VPN access for secure remote connections
- Integrate with other homelab services (Home Assistant, Jellyfin, etc.)
- Scale to a more powerful server as your needs grow
Your personal cloud is now under your control. Enjoy complete privacy and independence from corporate data centers.