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.

Andreas · April 13, 2026 · 12 min read

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 .env to version control
  • Change NC_TRUSTED_DOMAINS and NC_OVERWRITE_HOST to 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.

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:

  1. Data folder: Should already be set to /var/www/html/data. Leave as-is.
  2. Database: Leave as-is (MariaDB detected automatically).
  3. 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:

  1. 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
  1. Enable Redis caching: Settings → Administration → System

    • You should see a green checkmark next to "Memory caching" if Redis is detected.
  2. Increase PHP limits if needed (editing the docker-compose.yml and restarting).

  3. Enable SSL verification: Settings → Administration → System → Enable SSL certification verification (unless using self-signed certificates).

  4. 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:

  1. Check if Redis is running:
docker-compose ps redis
  1. Check Pi resource usage:
htop

If RAM is full, increase swap or upgrade to Pi 5 with 8GB RAM.

  1. Check database slow query log:
docker exec nextcloud-db tail -f /var/log/mysql/slow.log
  1. Restart everything:
docker-compose down && docker-compose up -d

Mobile client won't sync

Symptom: Files aren't syncing to phone.

Fix:

  1. Check that client can reach server (test in browser first).
  2. Verify SSL certificate is valid (check browser warning).
  3. Log out of mobile app and log back in.
  4. On iOS: Check Settings → Nextcloud → permissions.
  5. 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.

Related Tools

Comments