Skip to content
AdvancedPi 4 / 5raspberry-pinextcloudnas

Self-Hosted Nextcloud NAS on Raspberry Pi 4

Build a private, durable Nextcloud NAS on a Raspberry Pi 4 -- SSD boot instead of a failure-prone SD card, Docker Compose with Redis caching, Tailscale for remote access with zero ports forwarded, and an automated backup strategy that actually runs.

SolderHub10/17/2026 22 min read 4 views
Self-Hosted Nextcloud NAS on Raspberry Pi 4

Most "install Nextcloud on a Raspberry Pi" guides stop at docker-compose up and a congratulations message. What they skip is everything that determines whether the result is actually usable day to day: whether an SD card can survive months of database writes without corrupting, how to reach the server securely from outside the house without opening ports to the entire internet, and what happens to years of family photos when -- not if -- the SD card eventually fails.

This guide builds a Nextcloud instance on a Raspberry Pi 4 designed around those realities from the start: boot and application data on a USB SSD instead of an SD card, Tailscale for remote access instead of port forwarding, and an actual backup strategy instead of an afterthought. It assumes a Pi 4 with at least 4GB RAM, though 8GB is noticeably more comfortable once more than one or two people are syncing photos simultaneously.

Raspberry Pi 4 connected to a USB SSD enclosure, set up as a self-hosted Nextcloud NAS

Hardware and What Actually Matters

ComponentRecommendationWhy
Raspberry Pi 44GB minimum, 8GB betterNextcloud's PHP processes and a database are genuinely memory-hungry under concurrent use
Boot/OS storageUSB 3.0 SSD, 64GB+SD cards wear out under database write patterns; an SSD is dramatically more durable and faster
Bulk file storageExternal USB 3.0 HDD or SSD, sized to your dataCan be the same drive as boot storage for small setups, or separate for larger libraries
Power supplyOfficial Pi 4 USB-C, 3AUndersized supplies cause random reboots under SSD + network load, a classic false-flag "software bug"
Case with airflowAny case with a fan or heatsinksThe Pi 4 thermal-throttles under sustained load; a passive case with no airflow measurably slows file operations
Ethernet connectionStrongly recommended over WiFiNextcloud sync and photo backup are bandwidth-heavy; WiFi works but Ethernet removes an entire class of flaky-connection troubleshooting

Booting entirely from the SSD rather than an SD card is worth the extra setup step. Pi 4 units with an updated bootloader (anything sold in the last few years, or updated via raspi-config) can boot directly from USB with no SD card involved at all, which removes the single most common point of failure in long-running Pi NAS builds.

Step 1: Flash the OS and Enable USB Boot

Using Raspberry Pi Imager on another computer, flash Raspberry Pi OS Lite (64-bit) directly to the SSD rather than an SD card. The 64-bit build matters here -- Docker's official images and several Nextcloud dependencies perform meaningfully better on 64-bit ARM than the 32-bit build, and some newer container images have dropped 32-bit support entirely.

Before first boot, use the Imager's advanced options (the gear icon) to pre-configure SSH access, a hostname, and WiFi fallback credentials if needed -- this avoids a monitor-and-keyboard session entirely for a Pi that's going to live headless in a closet.

# On first SSH login, update everything before installing anything else:
sudo apt update && sudo apt full-upgrade -y
sudo reboot

If the Pi doesn't boot from the SSD on the first attempt, check raspi-config under Advanced Options → Boot Order and confirm USB Boot is selected -- on older Pi 4 units this sometimes requires a one-time bootloader update via sudo rpi-eeprom-update -a before USB boot is available at all.

Raspberry Pi Imager advanced settings showing SSH, hostname, and WiFi pre-configuration for headless setup

Step 2: Install Docker and Docker Compose

Docker is the recommended installation path here over a bare-metal Apache/PHP/MariaDB install -- it isolates Nextcloud's dependencies from the host OS, makes updates a single command instead of a multi-package upgrade dance, and makes the entire stack portable if you ever migrate to different hardware.

sudo apt install -y docker.io docker-compose curl wget
sudo usermod -aG docker $USER
newgrp docker

Log out and back in (or run newgrp docker as shown) so the group membership change takes effect without requiring a full reboot. Confirm the install with docker --version and docker-compose --version before moving on.

Step 3: The Docker Compose Stack

This stack runs four containers together: MariaDB for the database, Redis for caching (which meaningfully improves page-load responsiveness on Pi-class hardware), the Nextcloud application itself, and Caddy as a reverse proxy handling HTTPS automatically.

# docker-compose.yml
version: "3.8"

services:
  db:
    image: mariadb:10.11
    restart: unless-stopped
    command: --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED
    volumes:
      - ./db:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: nextcloud
      MYSQL_PASSWORD: ${DB_PASSWORD}

  redis:
    image: redis:7-alpine
    restart: unless-stopped

  app:
    image: nextcloud:28-apache
    restart: unless-stopped
    depends_on:
      - db
      - redis
    volumes:
      - ./nextcloud:/var/www/html
      - /mnt/storage:/var/www/html/data
    environment:
      MYSQL_HOST: db
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: nextcloud
      MYSQL_PASSWORD: ${DB_PASSWORD}
      REDIS_HOST: redis
      NEXTCLOUD_TRUSTED_DOMAINS: nextcloud.local

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
    depends_on:
      - app

volumes:
  caddy_data:

The --innodb-buffer-pool-size=256M flag on the database service is worth keeping even though it looks like a minor tuning detail -- without it, MariaDB's default buffer pool sizing assumptions (tuned for server-class hardware) can starve the Pi's limited RAM under concurrent access, and it's a common, quiet cause of Nextcloud feeling sluggish for no obvious reason.

Diagram showing the four-container Docker stack: MariaDB, Redis, Nextcloud, and Caddy reverse proxy

Step 4: Environment Variables and First Launch

Create a .env file alongside the compose file to keep credentials out of version control if this configuration is ever committed to a repository:

# .env
DB_ROOT_PASSWORD=use_a_real_generated_password_here
DB_PASSWORD=a_different_generated_password_here

Then bring the stack up:

docker-compose up -d
docker-compose logs -f app   # watch startup, ctrl-C once it settles

First boot takes a few minutes on a Pi 4 as Nextcloud finishes its internal setup. Once the logs settle, visiting the server's IP address in a browser presents the initial admin account setup screen -- create the admin account here, and Nextcloud automatically detects and connects to the MariaDB and Redis containers using the environment variables already configured.

Step 5: Mounting External Storage Correctly

The compose file above references /mnt/storage as the actual data location, mapped into the container. Before starting the stack for the first time, that mount point needs to exist and be a genuinely separate, properly-formatted drive rather than a folder on the boot SSD -- otherwise, bulk photo and file storage competes with the OS and database for the same limited SSD space.

# Identify the external drive
lsblk

# Format as ext4 (only on a fresh/empty drive -- this erases existing data)
sudo mkfs.ext4 /dev/sda1

# Create a mount point and mount it
sudo mkdir -p /mnt/storage
sudo mount /dev/sda1 /mnt/storage

# Make the mount persistent across reboots
echo '/dev/sda1 /mnt/storage ext4 defaults,nofail 0 2' | sudo tee -a /etc/fstab

The nofail option in that fstab line matters more than it looks -- without it, if the external drive isn't detected at boot for any reason (a loose USB connection, a drive that's slow to spin up), the Pi can fail to boot entirely rather than just running without that mount. nofail lets the system boot regardless and surface the missing mount as a service-level problem instead of a boot-level one.

Terminal session showing an external USB drive being formatted and mounted for Nextcloud data storage

Step 6: Remote Access Without Port Forwarding

Port-forwarding 80/443 to a home Pi is a common approach in older tutorials, and it works, but it also puts a self-hosted server directly on the open internet, where it becomes a target for automated scanning the moment it's reachable. Tailscale is a more defensible default for a personal NAS: it creates a private mesh VPN between your devices using WireGuard under the hood, with no ports opened on your router at all.

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up

That command prints a login URL -- authenticate it against your Tailscale account (free for personal use, up to a generous device limit), and the Pi gets a stable private IP address (something like 100.x.x.x) reachable from any other device on your Tailscale network, anywhere, without touching router configuration. Install the Tailscale app on a phone or laptop, and the Nextcloud instance becomes reachable exactly as if you were on the home network, from anywhere with internet access.

For access from devices that can't run Tailscale (a shared family tablet, for instance), Tailscale's Funnel feature can expose a single service publicly with HTTPS handled automatically, which is a narrower and more deliberate exposure than forwarding the router's ports directly.

Step 7: A Backup Strategy That Actually Runs

A NAS without backups is a single point of failure wearing a reassuring name. The simplest reliable pattern for a Pi-based Nextcloud instance is a nightly rsync to a second drive or a remote destination, paired with a database dump so the two stay consistent:

#!/bin/bash
# /home/pi/backup-nextcloud.sh

BACKUP_DIR="/mnt/backup-drive/nextcloud-backups/$(date +%F)"
mkdir -p "$BACKUP_DIR"

# Put Nextcloud into maintenance mode to avoid backing up a mid-write state
docker exec -u www-data nextcloud-app-1 php occ maintenance:mode --on

# Dump the database
docker exec nextcloud-db-1 mysqldump -u root -p"$DB_ROOT_PASSWORD" nextcloud > "$BACKUP_DIR/nextcloud-db.sql"

# Sync the data directory
rsync -avh --delete /mnt/storage/ "$BACKUP_DIR/data/"

# Bring Nextcloud back online
docker exec -u www-data nextcloud-app-1 php occ maintenance:mode --off

# Prune backups older than 14 days
find /mnt/backup-drive/nextcloud-backups/* -maxdepth 0 -mtime +14 -exec rm -rf {} \;

Schedule it with cron for the middle of the night:

# crontab -e
0 3 * * * /home/pi/backup-nextcloud.sh >> /home/pi/backup.log 2>&1

The maintenance-mode toggle around the backup window matters -- without it, a backup can capture files and database state from two different moments if something is actively syncing during the copy, leading to a restore that's subtly inconsistent. It costs a few minutes of read-only downtime overnight, which is a reasonable tradeoff for a backup that's actually trustworthy.

Backup script output showing a nightly automated rsync and database dump for a self-hosted Nextcloud instance

Performance Tuning for Pi-Class Hardware

A few settings meaningfully improve responsiveness on a Pi 4 specifically, beyond the database buffer pool tweak already covered:

Enable PHP OPcache and APCu. Add these to the Nextcloud container's PHP configuration (via a mounted php.ini override or environment-based config) -- OPcache caches compiled PHP bytecode, and APCu provides fast local caching for Nextcloud's own metadata operations. On a Pi 4, this combination is often the single biggest responsiveness improvement available without touching hardware.

Set up Nextcloud's background jobs via cron, not AJAX. The default "AJAX" background job trigger only runs maintenance tasks while a browser tab is open, which is unreliable for a headless server. Switch to system cron in the admin settings, and add a host-level cron entry calling docker exec -u www-data nextcloud-app-1 php cron.php every 5 minutes.

Disable file previews for video and unsupported formats if storage is tight. Preview generation is CPU-intensive on Pi-class hardware and can noticeably slow down large-folder browsing; scoping it down in config.php to only the formats you actually need previews for is a reasonable tradeoff.

Keep the Redis container in the loop for file locking, not just caching. The compose file above already wires this up via REDIS_HOST, but it's worth confirming under Settings → Administration → Overview that Nextcloud reports Redis as active -- this specifically prevents file-locking slowdowns during simultaneous multi-device syncs, a pattern that's noticeably worse on Pi hardware without it.

Nextcloud administration overview page showing Redis caching status and system health checks

Common Problems and Their Actual Fixes

"Access through untrusted domain" error after setup. Nextcloud's trusted_domains list in config.php needs every hostname or IP used to reach it -- including the Tailscale IP if that's how it's being accessed. Add each one as a new array entry rather than replacing the existing default.

Uploads fail or time out for larger files. Caddy's default request size limits, combined with PHP's upload_max_filesize and post_max_size, both need raising for anything beyond a few hundred megabytes. This is a two-place fix, not one -- raising only the PHP limit while Caddy still caps the request size upstream produces a confusing failure that looks like a Nextcloud bug but isn't.

The Pi becomes unresponsive during large file syncs. Usually a sign the SSD is doing double duty as both boot drive and bulk storage under sustained write load. Splitting OS/database onto one drive and bulk file storage onto a second physical drive, as outlined in the mounting section above, resolves this in most reported cases.

Photos app is slow to generate thumbnails for a large existing photo library. Expected on first import -- Nextcloud generates previews progressively as the library is browsed or via a background job, not all at once. Running docker exec -u www-data nextcloud-app-1 php occ preview:generate-all once, overnight, front-loads the work instead of letting it happen reactively during normal browsing.

Frequently Asked Questions

Is a Raspberry Pi 4 actually powerful enough for daily Nextcloud use? For one to three users -- syncing photos, documents, and calendars -- yes, comfortably, provided the SSD-boot and Redis-caching steps above are followed. Heavier use cases (video preview generation for a large family library, more than four or five simultaneous users) are where a Pi 5 or a small dedicated mini-PC starts to make a noticeably more comfortable difference.

Can I skip Docker and install Nextcloud directly on Raspberry Pi OS? Yes, and older guides do exactly that with Apache and PHP installed natively. The tradeoff is updates become a manual multi-package process instead of docker-compose pull && docker-compose up -d, and dependency versions are tied to whatever's in Raspberry Pi OS's repositories rather than the exact versions Nextcloud's own images ship with.

Do I need a domain name for this setup? Not if Tailscale is the only access method -- Tailscale's private network handles addressing internally, and the Caddyfile can be configured for a local hostname instead of a public domain with a real TLS certificate. A domain becomes worth adding only if public access via Tailscale Funnel, or traditional port forwarding, is specifically desired.

Multiple devices syncing photos and files to a self-hosted Nextcloud instance running on Raspberry Pi 4

Keeping the Stack Updated Safely

Docker makes updates low-friction, but "low-friction" isn't the same as "risk-free" -- a Nextcloud major version jump can occasionally require a maintenance window and, rarely, manual intervention if a third-party app isn't yet compatible with the new version. A safer update pattern than blindly pulling latest:

# Pin to a specific major version in docker-compose.yml (nextcloud:28-apache, not nextcloud:latest)
# Then, to update within that major version:
docker-compose pull
docker-compose up -d

# Check the upgrade completed cleanly:
docker exec -u www-data nextcloud-app-1 php occ status

Pinning to a major version rather than tracking latest means updates within that version (28.0.1, 28.0.2, and so on) apply automatically on each pull, but jumping from Nextcloud 28 to 29 requires deliberately editing the compose file's image tag -- a small extra step that turns a potentially disruptive major upgrade into a conscious decision rather than something that happens silently on a routine docker-compose pull. Nextcloud's own upgrade notes for each major release are worth a quick read before making that jump, since occasional app compatibility issues are called out there in advance.

Basic Monitoring Worth Setting Up

A Pi-based NAS running unattended benefits from a small amount of monitoring, even if it's just enough to catch a failing drive or a stalled backup before it becomes data loss. Two lightweight additions cover most of what matters:

A simple healthcheck cron job that emails or pushes a notification if the backup script from earlier fails to complete:

#!/bin/bash
# append to backup-nextcloud.sh, after the existing backup logic
if [ $? -ne 0 ]; then
  curl -s -X POST "https://ntfy.sh/your-topic-name" -d "Nextcloud backup failed on $(hostname)"
fi

(ntfy.sh is a free, simple push-notification service that needs no account for basic use -- any similar webhook-based notifier works the same way.)

SMART monitoring on the storage drive, since USB HDDs and SSDs do eventually fail, and catching early warning signs beats discovering a dead drive during a restore attempt:

sudo apt install -y smartmontools
sudo smartctl -a /dev/sda   # check current drive health
sudo systemctl enable smartd  # ongoing background monitoring

Neither of these takes more than a few minutes to set up, and both convert "the NAS silently stopped backing up three weeks ago" into a same-day notification instead of a surprise discovered during an actual data-loss event -- exactly the kind of unglamorous step that separates a NAS you can trust from one that merely looks finished.

Build Sheet

Take this build with you

We'll email you the parts list for Self-Hosted Nextcloud NAS on Raspberry Pi 4, with buy links and the Wokwi and source links, so you can order and build later. You'll also get new builds as they're published.

By subscribing you agree to our Privacy Policy. Unsubscribe anytime.

Q&A is currently disabled site-wide.

Related projects