← All articles
SELF-HOSTING Self-Hosting Nextcloud: Your Own Cloud Storage Platform 2026-02-09 · nextcloud · cloud-storage · self-hosted

Self-Hosting Nextcloud: Your Own Cloud Storage Platform

Self-Hosting 2026-02-09 nextcloud cloud-storage self-hosted docker

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):

Recommended (comfortable daily use with apps):

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:

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:

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:

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

  1. The data directory (/mnt/data/nextcloud) -- Your users' files. This is the irreplaceable data.
  2. The database -- Contains file metadata, user accounts, shares, calendar entries, contacts, and app configurations.
  3. The config directory -- Contains config.php with your instance's unique configuration. Stored inside the nextcloud-app volume.

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:

  1. Enter your server URL (e.g., https://cloud.yourdomain.com)
  2. Authenticate through the browser-based login flow
  3. Choose which folders to sync and where to store them locally
  4. 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:

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

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

  1. Back up everything first (database, data, config)
  2. Update the image tag in docker-compose.yml to the next major version
  3. Pull and restart
  4. Run the upgrade command
  5. 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:

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.