Google Drive knows everything about your files. Dropbox can access your data. iCloud locks you into Apple's ecosystem. What if you could have all the convenience of cloud storage with complete privacy?
Nextcloud is the open-source answer. It's a full-featured cloud platform that you host yourself—file sync, calendar, contacts, video calls, collaborative documents, and much more. All running on your own server.
This guide walks you through setting up Nextcloud on a VPS, including performance optimization, mobile apps, and backup strategies.
Why Self-Host Nextcloud?
| Feature | Google Drive | Dropbox | Nextcloud |
|---|---|---|---|
| Cost (2TB) | $10/mo | $12/mo | ~€9-15/mo (VPS+storage) |
| Data Ownership | Dropbox | 100% Yours | |
| Privacy | Scanned/indexed | E2E optional | Complete |
| Calendar/Contacts | Yes | No | Yes |
| Collaborative Docs | Google Docs | Paper | OnlyOffice/Collabora |
| Video Calls | Meet | No | Talk |
| Extensibility | Limited | Limited | 200+ apps |
Requirements
| Usage | Users | VPS Specs | DanubeData Plan |
|---|---|---|---|
| Personal | 1-3 | 2 vCPU, 4GB RAM | Standard (€8.99/mo) |
| Family/Small Team | 3-10 | 4 vCPU, 8GB RAM | Performance (€17.99/mo) |
| Organization | 10-50 | 4+ vCPU, 16GB RAM | Dedicated plans |
Storage options:
- VPS local storage: Included in VPS price
- S3 as primary storage: €3.99/TB/month (recommended for large libraries)
Step 1: Provision Your VPS
- Create a VPS on DanubeData
- Choose Ubuntu 24.04 LTS
- Select at least Standard plan (4GB RAM)
- Add extra storage if storing files locally
Step 2: Initial Server Setup
# SSH into your server
ssh root@YOUR_SERVER_IP
# Update system
apt update && apt upgrade -y
# Install Docker
curl -fsSL https://get.docker.com | sh
# Install Docker Compose
apt install -y docker-compose-plugin
# Configure firewall
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
# Set hostname
hostnamectl set-hostname nextcloud
Step 3: Configure DNS
# Add A record
Type: A
Name: cloud (or @ for root)
Value: YOUR_SERVER_IP
TTL: 300
# Verify
dig cloud.yourdomain.com +short
Step 4: Create Docker Compose Configuration
# Create directory
mkdir -p /opt/nextcloud
cd /opt/nextcloud
# Create docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: "3.8"
services:
db:
image: mariadb:11
container_name: nextcloud-db
restart: always
command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW
volumes:
- db:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
networks:
- nextcloud
redis:
image: redis:7-alpine
container_name: nextcloud-redis
restart: always
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis:/data
networks:
- nextcloud
app:
image: nextcloud:29-apache
container_name: nextcloud-app
restart: always
ports:
- 8080:80
volumes:
- nextcloud:/var/www/html
- data:/var/www/html/data
environment:
- MYSQL_HOST=db
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- REDIS_HOST=redis
- REDIS_HOST_PASSWORD=${REDIS_PASSWORD}
- NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER}
- NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD}
- NEXTCLOUD_TRUSTED_DOMAINS=${NEXTCLOUD_DOMAIN}
- OVERWRITEPROTOCOL=https
- OVERWRITEHOST=${NEXTCLOUD_DOMAIN}
- PHP_MEMORY_LIMIT=1G
- PHP_UPLOAD_LIMIT=16G
depends_on:
- db
- redis
networks:
- nextcloud
cron:
image: nextcloud:29-apache
container_name: nextcloud-cron
restart: always
volumes:
- nextcloud:/var/www/html
- data:/var/www/html/data
entrypoint: /cron.sh
depends_on:
- app
networks:
- nextcloud
volumes:
db:
redis:
nextcloud:
data:
networks:
nextcloud:
EOF
Create Environment File
# Generate passwords
MYSQL_ROOT_PASSWORD=$(openssl rand -hex 16)
MYSQL_PASSWORD=$(openssl rand -hex 16)
REDIS_PASSWORD=$(openssl rand -hex 16)
ADMIN_PASSWORD=$(openssl rand -hex 12)
# Create .env file
cat > .env << EOF
MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD
MYSQL_PASSWORD=$MYSQL_PASSWORD
REDIS_PASSWORD=$REDIS_PASSWORD
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=$ADMIN_PASSWORD
NEXTCLOUD_DOMAIN=cloud.yourdomain.com
EOF
# IMPORTANT: Save these credentials!
echo "Admin username: admin"
echo "Admin password: $ADMIN_PASSWORD"
echo ""
echo "Save this information securely!"
# Secure the file
chmod 600 .env
Step 5: Set Up Reverse Proxy (Caddy)
# Install Caddy
apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt update && apt install -y caddy
# Configure Caddy
cat > /etc/caddy/Caddyfile << 'EOF'
cloud.yourdomain.com {
reverse_proxy localhost:8080
# Required headers for Nextcloud
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
}
# Handle .well-known for CalDAV/CardDAV
redir /.well-known/carddav /remote.php/dav 301
redir /.well-known/caldav /remote.php/dav 301
# Large file uploads
request_body {
max_size 16GB
}
encode gzip
}
EOF
# Reload Caddy
systemctl reload caddy
Step 6: Launch Nextcloud
cd /opt/nextcloud
# Start all services
docker compose up -d
# Watch logs for startup
docker compose logs -f app
# Wait for "ready to handle connections" message
# Initial setup takes 2-3 minutes
Step 7: Initial Configuration
- Visit
https://cloud.yourdomain.com - Log in with admin credentials from .env
- Complete the setup wizard
Essential Configuration
# Enter the Nextcloud container
docker exec -it nextcloud-app bash
# Run as www-data user
su -s /bin/bash www-data
# Set background jobs to cron (already handled by cron container)
php occ background:cron
# Set default phone region
php occ config:system:set default_phone_region --value="DE"
# Set maintenance window (for background tasks)
php occ config:system:set maintenance_window_start --type=integer --value=1
# Enable APCu for local caching
php occ config:system:set memcache.local --value="\OC\Memcache\APCu"
# Exit container
exit
exit
Step 8: Install Essential Apps
Access Admin Settings → Apps and install:
- Calendar: CalDAV calendar sync
- Contacts: CardDAV contact sync
- Notes: Simple note-taking
- Tasks: To-do lists
- Talk: Video/audio calls
- OnlyOffice or Collabora Online: Document editing
- Photos: Photo gallery
- Deck: Kanban boards
Step 9: Configure Desktop/Mobile Sync
Desktop Client
- Download from nextcloud.com/install
- Enter server URL:
https://cloud.yourdomain.com - Log in with your credentials
- Choose folders to sync
Mobile Apps
- Download "Nextcloud" from App Store / Play Store
- Enter server URL and credentials
- Enable auto-upload for photos (optional)
Calendar/Contacts Sync
# CalDAV URL for calendar apps:
https://cloud.yourdomain.com/remote.php/dav/calendars/USERNAME/
# CardDAV URL for contact apps:
https://cloud.yourdomain.com/remote.php/dav/addressbooks/users/USERNAME/
# Works with:
# - iOS (built-in)
# - macOS (built-in)
# - Thunderbird
# - Android (DAVx⁵ app)
Step 10: External Storage (S3)
For large storage needs, use S3 as your primary storage:
Enable External Storage App
- Admin Settings → Apps
- Enable "External storage support"
Configure S3 Storage
- Admin Settings → External storage
- Add new external storage:
Folder name: S3 Files
External storage: Amazon S3
Configuration:
Bucket: your-nextcloud-bucket
Hostname: s3.danubedata.com
Port: 443
Region: eu-central-1
Enable SSL: Yes
Enable Path Style: Yes
Access key: your-access-key
Secret key: your-secret-key
Use S3 as Primary Storage
# Add to config.php (inside container)
docker exec -it nextcloud-app bash
cat >> /var/www/html/config/config.php << 'EOF'
'objectstore' => [
'class' => '\OC\Files\ObjectStore\S3',
'arguments' => [
'bucket' => 'nextcloud-primary',
'hostname' => 's3.danubedata.com',
'key' => 'your-access-key',
'secret' => 'your-secret-key',
'port' => 443,
'use_ssl' => true,
'region' => 'eu-central-1',
'use_path_style' => true,
],
],
EOF
Step 11: Performance Tuning
PHP Configuration
# Already set in docker-compose, but verify:
docker exec nextcloud-app php -i | grep memory_limit
# Should show: memory_limit => 1G
# Check upload limits
docker exec nextcloud-app php -i | grep upload_max_filesize
# Should show: upload_max_filesize => 16G
Database Optimization
# Add indexes (improves performance significantly)
docker exec -it nextcloud-app bash
su -s /bin/bash www-data
php occ db:add-missing-indices
php occ db:convert-filecache-bigint
exit
exit
Preview Generation
# Install preview generator app
docker exec -u www-data nextcloud-app php occ app:install previewgenerator
# Generate previews for existing files
docker exec -u www-data nextcloud-app php occ preview:generate-all
# Add to cron for new files
# Already handled by cron container
Step 12: Backup Strategy
#!/bin/bash
# /opt/nextcloud/backup.sh
set -e
BACKUP_DIR="/opt/nextcloud/backups"
DATE=$(date +%Y-%m-%d_%H-%M-%S)
S3_BUCKET="nextcloud-backups"
mkdir -p $BACKUP_DIR
echo "Starting Nextcloud backup: $DATE"
# Enable maintenance mode
docker exec -u www-data nextcloud-app php occ maintenance:mode --on
# Backup database
docker exec nextcloud-db mariadb-dump -u root -p$MYSQL_ROOT_PASSWORD nextcloud | gzip > "$BACKUP_DIR/db_$DATE.sql.gz"
# Backup config
docker cp nextcloud-app:/var/www/html/config/config.php "$BACKUP_DIR/config_$DATE.php"
# Backup docker-compose and .env
cp /opt/nextcloud/docker-compose.yml "$BACKUP_DIR/docker-compose_$DATE.yml"
cp /opt/nextcloud/.env "$BACKUP_DIR/env_$DATE"
# Disable maintenance mode
docker exec -u www-data nextcloud-app php occ maintenance:mode --off
# Upload to S3
rclone copy "$BACKUP_DIR" danubedata:$S3_BUCKET/
# Clean up old local backups
find $BACKUP_DIR -type f -mtime +7 -delete
echo "Backup complete: $DATE"
# Make executable and schedule
chmod +x /opt/nextcloud/backup.sh
# Add to crontab (daily at 3 AM)
(crontab -l 2>/dev/null; echo "0 3 * * * /opt/nextcloud/backup.sh >> /var/log/nextcloud-backup.log 2>&1") | crontab -
Step 13: Security Hardening
Enable Brute Force Protection
# Already enabled by default, verify:
docker exec -u www-data nextcloud-app php occ config:system:get auth.bruteforce.protection.enabled
# Should return: true
Two-Factor Authentication
- Install "Two-Factor TOTP" app
- Enable for your account in Personal Settings → Security
- Use with any authenticator app (Google Authenticator, Authy, etc.)
Fail2ban Integration
# Install fail2ban
apt install -y fail2ban
# Create Nextcloud filter
cat > /etc/fail2ban/filter.d/nextcloud.conf << 'EOF'
[Definition]
failregex = ^.*Login failed: '.*' (Remote IP: '').*$
^.*Trusted domain error: '.*' tried to access using .* as host.*(Remote IP: '').*$
ignoreregex =
EOF
# Create jail
cat > /etc/fail2ban/jail.d/nextcloud.local << 'EOF'
[nextcloud]
enabled = true
port = 80,443
protocol = tcp
filter = nextcloud
maxretry = 5
bantime = 3600
logpath = /var/lib/docker/volumes/nextcloud_nextcloud/_data/data/nextcloud.log
EOF
# Restart fail2ban
systemctl restart fail2ban
Updating Nextcloud
cd /opt/nextcloud
# Pull latest image
docker compose pull
# Stop containers
docker compose down
# Start with new version
docker compose up -d
# Run upgrade (if needed)
docker exec -u www-data nextcloud-app php occ upgrade
# Check status
docker exec -u www-data nextcloud-app php occ status
Cost Analysis
| Storage | Google Drive | Dropbox | Nextcloud (Self-Hosted) |
|---|---|---|---|
| 100 GB | $2/mo | $12/mo | €8.99/mo (VPS) |
| 1 TB | $10/mo | $12/mo | €8.99/mo (VPS) |
| 2 TB | $10/mo | $20/mo | €12.98/mo (VPS+S3) |
| 5 TB | $25/mo | N/A | €24.95/mo (VPS+S3) |
| + Calendar/Contacts | Included | Not available | Included |
| + Unlimited Users | Per user pricing | Per user pricing | Included |
Get Started Today
Ready to own your cloud?
- Create a VPS on DanubeData (€8.99/mo)
- Follow this guide to deploy Nextcloud
- Install desktop/mobile apps
- Migrate your files from Google/Dropbox
Recommended Setup:
- VPS: Standard €8.99/mo (4GB RAM) for personal use
- S3 Storage: €3.99/mo + usage for large file storage
- Total: ~€13/mo for a complete cloud replacement
Need help setting up Nextcloud? Contact our team—we're happy to assist.