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
| Feature | Google Drive | Dropbox | Nextcloud (Self-Hosted) |
| Storage | 15 GB free | 2 GB free | Unlimited (your disk) |
| Monthly Cost (2TB) | $9.99/mo | $11.99/mo | $0 (self-hosted) |
| Data Location | Google's servers | AWS | Your server |
| Privacy | Scanned for ads | Third-party access | Complete control |
| Office Suite | Google Docs | Paper (basic) | Collabora / OnlyOffice |
| Video Calls | Google Meet | No | Nextcloud Talk |
| Calendar/Contacts | Yes | No | CalDAV/CardDAV |
| Custom Apps | No | No | 300+ apps |
| GDPR Compliant | Complex | Complex | By 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:
| Platform | Client | Features |
| Windows | Nextcloud Desktop Client | File sync, virtual files (on-demand), end-to-end encryption |
| macOS | Nextcloud Desktop Client | Finder integration, selective sync, virtual files |
| Linux | Nextcloud Desktop Client | File manager integration, AppImage available |
| iOS | Nextcloud iOS App | Auto-upload photos, offline files, widget |
| Android | Nextcloud Android App | Auto-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:
| App | Purpose | Why Install |
| Calendar | CalDAV calendar | Replace Google Calendar |
| Contacts | CardDAV contacts | Replace Google Contacts |
| Talk | Video calls + chat | Replace Zoom/Slack |
| Mail | Email client | Webmail within Nextcloud |
| Notes | Markdown notes | Replace Google Keep |
| Deck | Kanban boards | Replace Trello |
| Photos | Photo gallery with AI | Replace Google Photos |
| Maps | Geotagged photo maps | Visualize 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.