Tutorial

Self-Host Nextcloud: Your Own Private Cloud Storage in 10 Minutes

May 02, 2026

Back to Blog

Why Self-Host Your Cloud Storage?

Google Drive, Dropbox, OneDrive — they are convenient, but they come with trade-offs that many individuals and organizations can no longer accept. Your files live on someone else's servers, governed by someone else's terms of service. You pay recurring subscription fees that increase over time. Storage limits force you to ration what you keep. And for businesses handling sensitive data, compliance regulations may outright prohibit storing files with third-party cloud providers.

Nextcloud is the open-source answer to all of these concerns. It gives you everything Google Workspace and Dropbox offer — file sync, sharing, calendars, contacts, video calls, office documents — but running on your hardware, under your control. And thanks to Docker, you can have it running in under 10 minutes.

Nextcloud vs Commercial Alternatives

FeatureGoogle DriveDropboxNextcloud (Self-Hosted)
Storage15 GB free2 GB freeUnlimited (your disk)
Monthly Cost (2TB)$9.99/mo$11.99/mo$0 (self-hosted)
Data LocationGoogle's serversAWSYour server
PrivacyScanned for adsThird-party accessComplete control
Office SuiteGoogle DocsPaper (basic)Collabora / OnlyOffice
Video CallsGoogle MeetNoNextcloud Talk
Calendar/ContactsYesNoCalDAV/CardDAV
Custom AppsNoNo300+ apps
GDPR CompliantComplexComplexBy design
What you need: A Linux server (VPS or dedicated) with at least 1 CPU core, 2 GB RAM, and enough storage for your files. A domain name pointed to your server's IP. Docker and Docker Compose installed. That is it.

Architecture Overview

Our Nextcloud stack consists of four components, each running in its own container:

Users
HTTPS
Nginx
Reverse Proxy
Nextcloud
PHP-FPM
MariaDB
Database

Step 1: Prepare the Directory Structure

$ mkdir -p ~/nextcloud/{db,data,config,nginx} $ cd ~/nextcloud

Step 2: Create the Docker Compose File

# docker-compose.yml services: db: image: mariadb:11 restart: unless-stopped command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW volumes: - ./db:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MYSQL_DATABASE: nextcloud MYSQL_USER: nextcloud MYSQL_PASSWORD: ${DB_PASSWORD} healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] interval: 10s timeout: 5s retries: 5 networks: - backend app: image: nextcloud:29-fpm-alpine restart: unless-stopped depends_on: db: condition: service_healthy volumes: - ./data:/var/www/html - ./config:/var/www/html/config environment: MYSQL_HOST: db MYSQL_DATABASE: nextcloud MYSQL_USER: nextcloud MYSQL_PASSWORD: ${DB_PASSWORD} NEXTCLOUD_ADMIN_USER: ${ADMIN_USER} NEXTCLOUD_ADMIN_PASSWORD: ${ADMIN_PASSWORD} NEXTCLOUD_TRUSTED_DOMAINS: ${DOMAIN} OVERWRITEPROTOCOL: https OVERWRITECLIURL: https://${DOMAIN} networks: - frontend - backend nginx: image: nginx:alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./data:/var/www/html:ro - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./certs:/etc/nginx/certs:ro depends_on: - app networks: - frontend cron: image: nextcloud:29-fpm-alpine restart: unless-stopped entrypoint: /cron.sh depends_on: - app volumes: - ./data:/var/www/html - ./config:/var/www/html/config networks: - backend networks: frontend: backend: internal: true

Step 3: Configure Environment Variables

# .env DB_ROOT_PASSWORD=change_this_to_a_strong_password_1 DB_PASSWORD=change_this_to_a_strong_password_2 ADMIN_USER=admin ADMIN_PASSWORD=change_this_to_a_strong_password_3 DOMAIN=cloud.yourdomain.com
Change every password! Use openssl rand -base64 32 to generate strong, random passwords. Never use the example values. Add .env to your .gitignore immediately.

Step 4: Configure the Nginx Reverse Proxy

# nginx/nginx.conf worker_processes auto; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; client_max_body_size 10G; fastcgi_buffers 64 4K; upstream php-handler { server app:9000; } server { listen 80; return 301 https://$host$request_uri; } server { listen 443 ssl http2; ssl_certificate /etc/nginx/certs/fullchain.pem; ssl_certificate_key /etc/nginx/certs/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Content-Type-Options nosniff; add_header X-Frame-Options SAMEORIGIN; add_header X-XSS-Protection "1; mode=block"; add_header Referrer-Policy no-referrer; root /var/www/html; location = /robots.txt { allow all; log_not_found off; } location = /.well-known/carddav { return 301 $scheme://$host/remote.php/dav; } location = /.well-known/caldav { return 301 $scheme://$host/remote.php/dav; } location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update |status|ocs\/v[12]|updater\/.+|ocs-provider\/.+)\.php (?:$|\/) { fastcgi_split_path_info ^(.+?\.php)(\/.*|)$; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param HTTPS on; fastcgi_pass php-handler; } } }
client_max_body_size 10G is critical. Without this, nginx will reject file uploads larger than 1MB. Set this to the maximum file size you want to allow. The Nextcloud PHP configuration also needs to match — we will cover that in performance tuning.

Step 5: SSL Certificates with Let's Encrypt

Before starting the stack, you need SSL certificates. The simplest approach is using certbot standalone mode:

# Install certbot $ sudo apt install certbot # Get certificates (stop any service on port 80 first) $ sudo certbot certonly --standalone -d cloud.yourdomain.com # Copy certificates to the project $ mkdir -p ~/nextcloud/certs $ sudo cp /etc/letsencrypt/live/cloud.yourdomain.com/fullchain.pem ~/nextcloud/certs/ $ sudo cp /etc/letsencrypt/live/cloud.yourdomain.com/privkey.pem ~/nextcloud/certs/ $ sudo chown $USER:$USER ~/nextcloud/certs/*.pem

Step 6: Launch the Stack

$ cd ~/nextcloud $ docker compose up -d [+] Running 5/5 ✔ Network nextcloud_backend Created ✔ Network nextcloud_frontend Created ✔ Container nextcloud-db-1 Healthy ✔ Container nextcloud-app-1 Started ✔ Container nextcloud-nginx-1 Started ✔ Container nextcloud-cron-1 Started # Check all services are running $ docker compose ps NAME SERVICE STATUS PORTS nextcloud-app-1 app running 9000/tcp nextcloud-cron-1 cron running nextcloud-db-1 db running 3306/tcp nextcloud-nginx-1 nginx running 0.0.0.0:80->80, 0.0.0.0:443->443 # Follow logs during initial setup $ docker compose logs -f app

Open https://cloud.yourdomain.com in your browser. Nextcloud will complete its initial setup automatically using the environment variables you configured. Within a minute or two, you will see the login screen.

That is it! You now have a fully functional private cloud running on your own server. Log in with the admin credentials from your .env file and start uploading files.

Performance Tuning

A default Nextcloud installation works, but a few optimizations make it significantly faster.

PHP-FPM Tuning

Create a custom PHP configuration file to increase upload limits and optimize memory usage:

# custom-php.ini (mount into /usr/local/etc/php/conf.d/) upload_max_filesize = 10G post_max_size = 10G memory_limit = 1G max_execution_time = 3600 max_input_time = 3600 ; OPcache (massive performance boost) opcache.enable = 1 opcache.memory_consumption = 256 opcache.max_accelerated_files = 20000 opcache.revalidate_freq = 60 opcache.save_comments = 1 opcache.jit = 1255 opcache.jit_buffer_size = 128M

Database Optimization

Add database indices and tune MariaDB for better performance:

# Add missing indices (Nextcloud recommends this) $ docker compose exec -u www-data app php occ db:add-missing-indices Check indices of the share table. Check indices of the filecache table. Adding additional index to the filecache table, this can take a while... Filecache table updated successfully. # Convert filecache columns for big files $ docker compose exec -u www-data app php occ db:convert-filecache-bigint

Background Jobs

By default, Nextcloud uses AJAX for background tasks, which only runs when users are actively browsing. The cron container in our Compose file handles this properly, but you need to tell Nextcloud to use it:

# Set cron as the background job mechanism $ docker compose exec -u www-data app php occ background:cron Set mode for background jobs to 'cron'

Adding an Office Suite

To edit documents, spreadsheets, and presentations directly in Nextcloud, you need an office backend. The two main options are Collabora Online and OnlyOffice.

Collabora Online

  • LibreOffice-based rendering
  • Excellent compatibility with .odt, .ods, .odp
  • Official Nextcloud integration
  • Lower resource usage

Best for: Document fidelity

OnlyOffice

  • Better Microsoft Office compatibility
  • Modern, polished interface
  • Co-editing is smoother
  • Higher resource usage

Best for: MS Office workflows

Add Collabora to your Compose file:

# Add to docker-compose.yml collabora: image: collabora/code restart: unless-stopped environment: - aliasgroup1=https://cloud.yourdomain.com:443 - extra_params=--o:ssl.enable=false cap_add: - MKNOD networks: - frontend

Mobile and Desktop Sync

One of Nextcloud's greatest strengths is its client ecosystem. Sync clients are available for every platform:

PlatformClientFeatures
WindowsNextcloud Desktop ClientFile sync, virtual files (on-demand), end-to-end encryption
macOSNextcloud Desktop ClientFinder integration, selective sync, virtual files
LinuxNextcloud Desktop ClientFile manager integration, AppImage available
iOSNextcloud iOS AppAuto-upload photos, offline files, widget
AndroidNextcloud Android AppAuto-upload media, DAVx5 calendar/contacts sync
Auto-Upload Photos: Both mobile apps support automatic photo upload, making Nextcloud a direct replacement for Google Photos or iCloud Photos. Set it to upload over Wi-Fi only to save mobile data.

Security Hardening

1
Enable Brute-Force Protection: Nextcloud includes built-in brute force protection that slows down repeated failed login attempts. It is enabled by default but verify it: Settings > Security.
2
Two-Factor Authentication: Install the TOTP app from the Nextcloud app store. Each user can then enable 2FA from their security settings using any authenticator app.
3
Server-Side Encryption: Enable encryption for files at rest. This protects data if the physical disk is stolen. Navigate to Settings > Security > Server-side encryption.
4
Security Headers: Our nginx configuration already includes HSTS, X-Frame-Options, and other headers. Verify with the Nextcloud security scan at scan.nextcloud.com.
5
Fail2Ban Integration: Add a Fail2Ban jail for Nextcloud to block IPs after multiple failed login attempts. Nextcloud logs authentication failures to data/nextcloud.log.

Backup Strategy

Your Nextcloud installation has three components that need backing up:

1. Database

$ docker compose exec db \ mysqldump -u root \ -p"$DB_ROOT_PASSWORD" \ nextcloud > backup.sql

2. Data Directory

$ rsync -avz \ ~/nextcloud/data/ \ /backup/nextcloud-data/
# Complete backup script #!/bin/bash BACKUP_DIR=/backup/nextcloud/$(date +%Y%m%d) mkdir -p $BACKUP_DIR # Enable maintenance mode docker compose exec -u www-data app php occ maintenance:mode --on # Backup database docker compose exec db mysqldump -u root -p"$DB_ROOT_PASSWORD" \ nextcloud | gzip > $BACKUP_DIR/database.sql.gz # Backup data and config rsync -az ~/nextcloud/data/ $BACKUP_DIR/data/ rsync -az ~/nextcloud/config/ $BACKUP_DIR/config/ # Disable maintenance mode docker compose exec -u www-data app php occ maintenance:mode --off echo "Backup completed: $BACKUP_DIR"
Test your backups! A backup that you have never restored is not a backup — it is a hope. Practice the restore process at least once before you actually need it.

Deploying Nextcloud with Panelica

You can deploy Nextcloud on any Panelica-managed server using Docker with one-click app templates, with automatic SSL and reverse proxy configuration. The panel handles the entire workflow: creating the Docker Compose stack, configuring nginx as a reverse proxy for your Nextcloud domain, provisioning Let's Encrypt SSL certificates, and managing database credentials. What took us several manual steps in this tutorial becomes a point-and-click operation through the panel's Docker management interface.

Essential Nextcloud Apps

Nextcloud's app store contains over 300 apps. Here are the most valuable ones to install after your initial setup:

AppPurposeWhy Install
CalendarCalDAV calendarReplace Google Calendar
ContactsCardDAV contactsReplace Google Contacts
TalkVideo calls + chatReplace Zoom/Slack
MailEmail clientWebmail within Nextcloud
NotesMarkdown notesReplace Google Keep
DeckKanban boardsReplace Trello
PhotosPhoto gallery with AIReplace Google Photos
MapsGeotagged photo mapsVisualize photo locations

Conclusion

Self-hosting Nextcloud gives you complete control over your data, unlimited storage limited only by your disk space, and a feature set that rivals or exceeds commercial alternatives — all for the cost of a modest VPS. With Docker Compose, the deployment is reproducible, portable, and easy to maintain. The initial setup takes about 10 minutes, and the ongoing maintenance is minimal: update images periodically, back up your data, and keep your SSL certificates fresh.

Start with the basic file sync functionality, then explore the app ecosystem. Calendar and contacts sync alone make Nextcloud worth running. Add the office suite for document collaboration. Install Talk for private video calls. Before long, you will have replaced half a dozen commercial services with a single, self-hosted platform that you own entirely.

Share:
See the Demo