Introduction
In light of current events — waves around — putting your own private data into a US based cloud, regardless of whether you are a US citizen or a EU citizen or somewhere else, might not be the most advisable option anymore. Especially if it's not encrypted, as it can then be scanned, AI trained on and many more things.
So I set out to run my own Nextcloud — pretty much a European open-source cloudware that is a bit akin to Google Workspace without email hosting.
What I wanted to achieve:
- Low costs
- Controllable costs (I want them predictable - not like an AWS bill)
- European data center
- Expandable storage without rebuilding the server
- Fully encrypted with proper key separation
- Syncing of files, calendar, contacts and the ability to host my own video calls à la Zoom
The key security insight: Nextcloud's server-side encryption stores the encryption keys in the same data directory as the encrypted files. If you simply point your data directory at a Storage Box, both your encrypted files AND the keys to decrypt them live in the same place — defeating much of the purpose.
This guide takes a different approach: encryption keys stay on your LUKS-encrypted VPS disk, while the bulk encrypted file storage lives on the cheap, expandable Storage Box. If someone gains access to your Storage Box, they get only encrypted blobs with no way to decrypt them.
Note for existing users: A previous version of this guide stored the entire data directory on the Storage Box, which meant encryption keys and encrypted files lived together. If you followed the earlier guide, see the Migration from Previous Guide section for steps to improve your security posture.
The setup has survived stress tests of 120,000+ files, multiple reboots, and months of daily use.
What this guide covers:
- Setting up the system with proper encryption architecture
- Getting Nextcloud running with Nextcloud AIO
- Configuring per-user storage offloading to the Storage Box
What this guide does NOT cover:
- Day-to-day Nextcloud usage and administration
- Maintaining and updating the system long-term
The Nextcloud desktop client for Mac and Windows supports virtual file systems. This means you can sync some folders fully to your device while having others available on-demand through the file provider API — useful for large archives you don't need locally all the time.
Architecture Overview
Before diving into the setup, it helps to understand what we're building and why.
The Problem with Naive Setups
When you enable Nextcloud's server-side encryption, it creates a files_encryption folder containing the keys needed to decrypt your files. If your entire data directory lives on remote storage (like a Storage Box), then anyone with access to that storage has both:
- Your encrypted files
- The keys to decrypt them
This is like locking your front door and leaving the key under the doormat.
Our Solution: Key Separation
┌─────────────────────────────────────────────────────────────────────────┐
│ VPS (LUKS-encrypted disk) │
│ │
│ Nextcloud Data Directory (local Docker volume) │
│ ├── files_encryption/ ← Master encryption keys (NEVER leave disk) │
│ ├── appdata_*/ ← App cache and config │
│ │ │
│ └── <username>/ │
│ ├── files_encryption/ ← Per-user keys (stay local) │
│ ├── cache/ ← User cache (stays local) │
│ ├── files/ ─┐ │
│ ├── files_trashbin/ ├─ Bind mounts to Storage Box │
│ ├── files_versions/ │ (only encrypted content travels there) │
│ └── uploads/ ─┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
│ bind mounts (per-user, 4 folders each)
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Storage Box (SMB mount) │
│ │
│ └── <username>/ │
│ ├── files/ ← Encrypted blobs only │
│ ├── files_trashbin/ ← Encrypted │
│ ├── files_versions/ ← Encrypted │
│ └── uploads/ ← Encrypted │
└─────────────────────────────────────────────────────────────────────────┘Security Properties
| Component | Location | Protected by |
|---|---|---|
| Encryption keys | VPS local disk | LUKS full-disk encryption (boot passphrase) |
| Encrypted files | Storage Box | Nextcloud server-side encryption |
| Nextcloud config | VPS local disk | LUKS full-disk encryption |
What this means in practice:
- Storage Box compromised? Attacker gets encrypted blobs, useless without keys.
- VPS disk stolen while powered off? Protected by LUKS encryption.
- VPS compromised while running? Keys are in memory — this is outside our threat model (if you need protection against this, don't run on shared infrastructure).
The Trade-off: Per-User Setup
This architecture requires adding bind mounts for each user you want to offload to the Storage Box. New users default to local storage (secure by default), and you explicitly choose which users to offload. This is a small administrative overhead for significantly better security.
Why I care about encryption
It might not be often, but it does happen that law enforcement confiscates physical hardware in a data center for analysis, or that people get unauthorised physical access. By requiring manual entry of the encryption key after a power event, I get to decide whether to unlock my data or not. Storing the encryption key at boot is like taping it next to your door outside your house "just to have it handy all the time".
Prerequisites
- VPS on Hetzner (in their parlance a cloud server) with 4GB of RAM, 2 CPU cores and 40GB of NVME storage
- A Hetzner Storage Box — essentially a 1TB+ NAS in the cloud for an incredibly low price
- A domain name with the ability to set A and AAAA records
- Basic familiarity with Linux command line and SSH
Both resources should be in the same Hetzner region to keep traffic internal and latency low.
On RAM: 4GB is comfortable. 2GB can work if you set up swap (covered later), but you may experience slowdowns during heavy operations like full-text search indexing.
Step 1 - Creating the server
Create a new server with the architecture type x86 (!important!). With Hetzner, you can use CX23, for example. The 2 CPU version is sufficient. Even only 2 GB can work RAM wise — I will show you how.
After you created the server, follow this guide:
How to install Ubuntu 24.04 with full disk encryption
Beware of the special section for Debian 13 with the following caveats:
-
When booted into the rescue system, you can check the full name of the Debian image by running
ls /root/imagesand copying the Debian 13 image name into your pasteboard. -
Your
setup.confshould look like this:CRYPTPASSWORD secret DRIVE1 /dev/sda BOOTLOADER grub HOSTNAME host.example.com PART /boot ext4 1G PART / ext4 all crypt IMAGE /root/images/Debian-1302-trixie-amd64-base.tar.gz SSHKEYS_URL /tmp/authorized_keys -
Additional notice: If you add private networking it might happen that your Dropbear unlock only picks up the IP from your local network first and is unreachable over ssh. In this case temporarily disable the private network or discuss this issue with your preferred coding AI — the issue is that the private network wins the race of "which network interface responds with an IP address first".
Once that is setup, you can create yourself a Storage Box in the same region via Hetzner Console. Choose your preferred size. Create a subaccount with limited access to a specific subfolder for your Nextcloud and enable SMB access. You DON'T need to enable "external access" though as this stays in the Hetzner network.
Step 2 - Booting into the server
Now you can proceed with setting up your VPS. You can follow this guide for a basic setup:
Initial Server Setup with Ubuntu
Let's open the relevant ports on UFW for all the stuff:
sudo ufw default deny incoming
sudo ufw default allow outgoing
# HTTP for ACME/Nextcloud challenge
sudo ufw allow 80/tcp comment 'ACME-HTTP-Nextcloud'
# HTTPS for Apache container (HTTP/1.1 & HTTP/2)
sudo ufw allow 443/tcp comment 'Apache-HTTPS'
# HTTP/3 (QUIC) for Apache container
sudo ufw allow 443/udp comment 'Apache-HTTP3-QUIC'
# Admin interface of master container
sudo ufw allow 8443/tcp comment 'Master-UI-HTTPS'
# TURN server (Talk container) – TCP & UDP
sudo ufw allow 3478/tcp comment 'TURN-TCP'
sudo ufw allow 3478/udp comment 'TURN-UDP'Once that is done, you can check your IPv4 address (if you ordered one) and IPv6 by running this command on the server:
ip -6 addr showYour interface is probably enp1s0.
Step 3 - Pointing DNS to your server
If you now go to the domain registrar of your own domain, you can adjust the (sub)domain's A and AAAA records for your IPv4 and IPv6 addresses respectively.
Once that is done, let's go back to your VPS.
Step 4 - Creating the SMB mount to the Storage Box
Back on the Hetzner VPS, go to a root shell with sudo su. Stay in the root shell during the rest of the guide.
This mount will hold the encrypted file content for users. It is NOT the Nextcloud data directory — that stays on the local LUKS-encrypted disk. We'll bind-mount specific user folders from here into the data directory later.
First, install SMB support:
apt update
apt install cifs-utilsCreate the mount point (replace myshare with whatever you want to call it):
mkdir -p /mnt/myshareCreate a credentials file (our disk is encrypted, so storing credentials here is acceptable):
mkdir -p /etc/cifs-creds
nano /etc/cifs-creds/myshareAdd:
username=your_smb_username
password=your_smb_passwordThis username and password comes from your Storage Box sub account.
Secure the credentials file:
chmod 600 /etc/cifs-creds/myshareNow create a systemd .mount unit:
nano /etc/systemd/system/mnt-myshare.mountImportant: The unit filename must match the mount path exactly, with
/replaced by-and the leading slash removed. So/mnt/mysharebecomesmnt-myshare.mount.
Add the following content:
Replace
YOURSTORAGEBOXUSER-subXwith your actual account name.
[Unit]
Description=Mount SMB Share myshare
DefaultDependencies=no
After=network-online.target
Wants=network-online.target
[Mount]
What=//YOURSTORAGEBOXUSER-subX.your-storagebox.de/YOURSTORAGEBOXUSER-subX
Where=/mnt/myshare
Type=cifs
Options=credentials=/etc/cifs-creds/myshare,iocharset=utf8,uid=33,gid=33,seal,vers=3.1.1,_netdev
TimeoutSec=30
[Install]
WantedBy=multi-user.targetA few notes on this unit:
DefaultDependencies=noprevents systemd from adding automatic dependencies that can cause ordering cycles with network-dependent mountsuid=33,gid=33sets ownership towww-data(the user Nextcloud runs as)sealenables SMB encryption in transit_netdevtells the system this is a network mount
Activate it:
systemctl daemon-reload
systemctl enable --now mnt-myshare.mountIf you get an error message like
Failed to mount mnt-myshare.mount - Mount SMB Share myshare, double-check in Hetzner Console if the subaccount has "Allow SMB" enabled.
Verify it mounted:
mount | grep myshare
ls -la /mnt/myshareYou should see an empty directory owned by www-data.
Step 5 - Installing Docker
Follow the official guide to install Docker: https://docs.docker.com/engine/install/debian/
Once installed, make Docker wait for the SMB mount to be ready:
systemctl edit docker.serviceAdd above the line that says ### Edits below this comment will be discarded:
[Unit]
Requires=mnt-myshare.mount
After=mnt-myshare.mountReload systemd:
systemctl daemon-reloadFinally, enable IPv6 support for Docker (we're living in the 21st century): https://github.com/nextcloud/all-in-one/blob/main/docker-ipv6-support.md
You won't be able to verify IPv6 works until Nextcloud is running — that comes later.
Step 6 - Preparing Nextcloud AIO
I recommend running Nextcloud AIO through compose. This makes it easier to track changes and see the startup parameters.
mkdir -p ~/containers/nextcloud
cd ~/containers/nextcloud
nano compose.ymlStart with the official compose file from AIO:
github.com/nextcloud/all-in-one/blob/main/compose.yaml
Make the following adjustments:
-
Uncomment
environment: -
Uncomment
NEXTCLOUD_DATADIR:and set it explicitly to the local Docker volume:NEXTCLOUD_DATADIR: /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/Important: Do NOT point this at your SMB mount. The data directory must stay on the local LUKS-encrypted disk so that encryption keys remain separate from encrypted files. We'll bind-mount only the user file storage to the SMB mount later.
-
Uncomment
NEXTCLOUD_MAX_TIME:and increase the value (I use7200) -
Uncomment
NEXTCLOUD_MEMORY_LIMIT:and set it to2048 -
Uncomment
environment:aboveNEXTCLOUD_MAX_TIME:andNEXTCLOUD_MEMORY_LIMIT: -
If you plan to use full-text search, uncomment
FULLTEXTSEARCH_JAVA_OPTIONS:and set reasonable values:FULLTEXTSEARCH_JAVA_OPTIONS: "-Xms1024M -Xmx2048M"
Step 7 - Setting up swap to help with memory pressure
Before we start running any containers, let's enable Swap on the server so that we don't run out of RAM:
fallocate -l 4G /swapfile
dd if=/dev/zero of=/swapfile bs=1M count=4096 status=progress
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfileLet's add it to system boot up:
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstabEncourage a bit more swapping:
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.confYou can verify it worked by checking:
htopIt should show memory and swap separately. Swap should show x/4.00G.
Especially if you are on the 2GB RAM box, 4G of Swap were a good idea when also running fulltext search
Step 8 - Starting it up
Make sure you're in the right directory:
If you haven't yet adjusted the DNS on your domain registrar to point to your Hetzner server, now is the time.
cd ~/containers/nextcloud
docker compose up -dNow continuing the guide on nextcloud/all-in-one/blob/main/readme.md, let's open https://example.com:8443 and go to your AIO install interface. Use your own domain.
If
https://example.com:8443doesn't load for you, tryhttps://example.com:8080instead.
Follow the steps. At the end it will show a temporary password for the user admin that you can then use to login under https://example.com:443 with the data store in the back being on your Storage Box.
Step 9 - Enabling encryption
Once logged in to the Nextcloud, you should:
| Description | |
|---|---|
| Encrypt all files | Head to your user icon on the top right => Admin settings => security settings => server side encryption => switch it on |
| Activate the encryption module | Head to your user icon on the top right => apps => deactivated apps and activate the "default encryption module" |
| Encrypt user home folders | Head back to admin settings => security settings => encryption Check that the checkbox is ticked for "encrypt user home folders" |
If you want, you can stop your containers, enable fulltextsearch and some other containers.
Step 10 - Setting up per-user storage offloading
Now that Nextcloud is running with encryption enabled, we'll configure specific users to store their files on the Storage Box while keeping encryption keys local.
How it works: Each Nextcloud user has four storage folders:
files/— their actual filesfiles_trashbin/— deleted files (before permanent deletion)files_versions/— version historyuploads/— temporary storage during uploads
We create these folders on the Storage Box, then bind-mount them into the Nextcloud data directory. Nextcloud sees them as local folders, but the data actually lives on the Storage Box — encrypted.
Setting up a user (example: admin)
First, check that Nextcloud has created the user's folder structure:
ls -la /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/admin/You should see files/ at minimum. If the other folders don't exist yet, they'll be created when needed.
Create the corresponding folder structure on the Storage Box:
mkdir -p /mnt/myshare/_data/admin/{files,files_trashbin,files_versions,uploads}
chown -R www-data:www-data /mnt/myshare/_data/adminCreate the target directories in the Docker volume (if they don't exist):
mkdir -p /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/admin/{files,files_trashbin,files_versions,uploads}
chown -R www-data:www-data /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/adminAdd the bind mounts to /etc/fstab:
nano /etc/fstabAdd these lines (adjust myshare to your mount name):
# Nextcloud user: admin
/mnt/myshare/_data/admin/files /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/admin/files none bind,nofail,x-systemd.requires-mounts-for=/mnt/myshare,x-systemd.device-timeout=30s 0 0
/mnt/myshare/_data/admin/files_trashbin /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/admin/files_trashbin none bind,nofail,x-systemd.requires-mounts-for=/mnt/myshare,x-systemd.device-timeout=30s 0 0
/mnt/myshare/_data/admin/files_versions /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/admin/files_versions none bind,nofail,x-systemd.requires-mounts-for=/mnt/myshare,x-systemd.device-timeout=30s 0 0
/mnt/myshare/_data/admin/uploads /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/admin/uploads none bind,nofail,x-systemd.requires-mounts-for=/mnt/myshare,x-systemd.device-timeout=30s 0 0Understanding the mount options:
bind— this is a bind mount, not a device mountnofail— boot continues even if mount fails (prevents boot hang)x-systemd.requires-mounts-for=/mnt/myshare— wait for SMB mount firstx-systemd.device-timeout=30s— timeout if mount takes too long
Mount them now:
systemctl daemon-reload
mount -aVerify they're active:
mount | grep admin/files
mount | grep admin/uploadsYou should see four bind mounts.
Usernames with spaces
If a username contains spaces (like "Hans Werner"), escape spaces with \040 in fstab:
/mnt/myshare/_data/Hans\040Werner/files /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/Hans\040Werner/files none bind,nofail,x-systemd.requires-mounts-for=/mnt/myshare,x-systemd.device-timeout=30s 0 0What about existing files?
If the user already has files in Nextcloud before you set up the bind mount:
- Stop Nextcloud containers:
cd ~/containers/nextcloud && docker compose down - Move existing files to Storage Box:
mv /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/admin/files/* /mnt/myshare/_data/admin/files/ - Set up bind mounts as described above
- Start Nextcloud:
docker compose up -d - Optionally, run a file scan:
docker exec -it nextcloud-aio-nextcloud php occ files:scan admin
Important notes
- New users default to local storage. This is secure by default — encryption keys and files are both on the LUKS disk. You choose which users to offload.
- Don't bind-mount
files_encryption! That folder contains encryption keys and must stay on the local disk. - Changes are per-user. You can have some users on local storage and others offloaded to the Storage Box.
Step 11 - Adding the mounts verification service
Now that you have bind mounts configured, we'll add a safety check that prevents Docker from starting if the mounts aren't ready. This avoids a situation where Nextcloud starts with unmounted directories and writes files to the wrong location.
Create the verification service
nano /etc/systemd/system/nextcloud-mounts-check.serviceAdd the following (adjust usernames to match your setup):
[Unit]
Description=Verify Nextcloud bind mounts are active
After=local-fs.target remote-fs.target mnt-myshare.mount
Requires=mnt-myshare.mount
[Service]
Type=oneshot
ExecStart=/bin/bash -c '\
mountpoint -q /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/admin/files'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.targetFor each additional user with bind mounts, add another mountpoint check with &&:
ExecStart=/bin/bash -c '\
mountpoint -q /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/admin/files && \
mountpoint -q /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/justin/files && \
mountpoint -q "/var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/Hans Werner/files"'Note: Usernames with spaces need to be quoted within the bash command.
Enable the service:
systemctl daemon-reload
systemctl enable nextcloud-mounts-check.serviceUpdate Docker to require the verification
systemctl edit docker.serviceUpdate the override to include the mounts-check service:
[Unit]
Requires=mnt-myshare.mount nextcloud-mounts-check.service
After=mnt-myshare.mount nextcloud-mounts-check.serviceApply the changes:
systemctl daemon-reloadTest it
Reboot and verify everything comes up correctly:
rebootAfter the system is back up:
# Check the mounts verification passed
systemctl status nextcloud-mounts-check.service
# Check Docker started successfully
systemctl status docker.service
# Check the bind mounts are active
mount | grep nextcloud_aio_nextcloud_dataMaintaining the verification service
Whenever you add bind mounts for a new user (Step 10), remember to:
- Add a
mountpointcheck for that user to the service - Reload:
systemctl daemon-reload
This is a small bit of maintenance, but it prevents silent failures where files end up in the wrong place.
Step 12 - Enabling Nextcloud backup (Optional)
Nextcloud AIO includes built-in borg backup. This backs up your Nextcloud configuration, database, and importantly, runs the automatic update process. We'll configure it to back up to the Storage Box while excluding the large data directory.
Create a backup subaccount
Create a new sub account on your Storage Box with:
- Access restricted to a backup folder
- SSH access only (no SMB needed)
Configure borg in AIO
In the AIO interface, enter your backup destination:
ssh://YOURBOXID-subX@YOURBOXID-subX.your-storagebox.de:23/./nextcloud-aio-borgThe . gets you to the backup sub folder. The additional subfolder is necessary.
Set up SSH authentication
Before borg can connect, you need to add its public key to the Storage Box. Copy the key shown in the AIO interface, then on your VPS:
nano authorized_keysPaste the key and save. Then copy it to the Storage Box:
scp -P 23 authorized_keys YOURBOXID-subX@YOURBOXID-subX.your-storagebox.de:.ssh/authorized_keysNow borg should be able to connect.
Exclude the data directory
To prevent borg from backing up all user files (which would be slow and redundant), add an exclusion marker:
touch /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/.noaiobackupRecommended: Enable VPS and Storage Box snapshots
The borg backup covers Nextcloud configuration and database, but for comprehensive data protection:
- VPS snapshots: Enable automatic snapshots in Hetzner Console for your VPS. This protects your encryption keys and local data.
- Storage Box snapshots: Enable automatic snapshots on your Storage Box. This protects your (encrypted) user files.
Together with borg backup, this gives you:
| Data | Protected by |
|---|---|
| Nextcloud config & database | Borg backup |
| Encryption keys | VPS snapshots |
| User files (encrypted) | Storage Box snapshots |
A note on backup strategy
With ransomware in mind, consider keeping an additional offline or off-site copy. If you have a Synology or similar NAS, you can sync via WebDAV (find the link in Nextcloud's Files app → Settings → WebDAV) and make local versioned backups with Hyper Backup.
Step 13 - Final considerations
Expanding storage
Storage Box: Increasing your Storage Box size immediately increases available space for user files. No action needed on the VPS — the SMB mount sees the new capacity automatically.
VPS local disk: If you need more space for the LUKS-encrypted local disk (encryption keys, database, app data), you'll need to:
- Shut down the VPS
- Resize the disk in Hetzner Console
- Boot the VPS normally
- Expand the partition and LUKS container:
# Grow the partition (adjust partition number as needed)
growpart /dev/sda 2
# Resize the LUKS container to fill available space
cryptsetup resize luks-<your-uuid>
# Resize the ext4 filesystem (can be done live)
resize2fs /dev/mapper/luks-<your-uuid>Tip: Take a VPS snapshot before resizing, just in case.
Adding new users workflow
When you create a new user in Nextcloud:
- The user's data defaults to local LUKS storage (secure by default)
- If you want to offload their files to the Storage Box:
- Create their folders on the Storage Box (Step 10)
- Add fstab entries (Step 10)
- Add a mountpoint check to
nextcloud-mounts-check.service(Step 11) - Run
systemctl daemon-reload && mount -a
Cleaning up leftovers
If you find leftover folders on the Storage Box from initial setup (like files_encryption or appdata_*), you can safely remove them — the real data lives on the local disk:
# Check what's there that shouldn't be
ls -la /mnt/myshare/_data/
# Remove leftovers (be careful!)
rm -rf /mnt/myshare/_data/files_encryption
rm -rf /mnt/myshare/_data/appdata_*Only the per-user folders (admin/, justin/, etc.) should exist on the Storage Box.
Migration from Previous Guide
If you followed an earlier version of this guide where NEXTCLOUD_DATADIR pointed directly to the Storage Box (/mnt/myshare), your encryption keys are currently stored alongside your encrypted files. This section describes how to migrate to the more secure architecture.
Understanding the risk
In the old setup:
- Encryption keys:
/mnt/myshare/_data/files_encryption/(on Storage Box) - Encrypted files:
/mnt/myshare/_data/<user>/files/(on Storage Box)
Anyone with access to your Storage Box has both the ciphertext AND the keys.
Migration overview
A. Stop Nextcloud
B. Copy encryption keys and config to local disk
C. Copy per-user local data
D. Update NEXTCLOUD_DATADIR to use local Docker volume
E. Set up per-user bind mounts for file storage
F. Start Nextcloud
Step-by-step migration
A. Stop Nextcloud and take backups
cd ~/containers/nextcloud
docker compose downTake a VPS snapshot and Storage Box snapshot before proceeding.
B. Create the local data directory and copy global files
mkdir -p /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_dataCopy encryption keys and essential files to local disk:
# Copy encryption keys (CRITICAL)
cp -a /mnt/myshare/_data/files_encryption /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/
# Copy appdata
cp -a /mnt/myshare/_data/appdata_* /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/
# Copy config files
cp -a /mnt/myshare/_data/.htaccess /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/
cp -a /mnt/myshare/_data/.ncdata /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/
cp -a /mnt/myshare/_data/.noaiobackup /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/
cp -a /mnt/myshare/_data/index.html /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/C. Copy per-user local data
For each user, copy the folders that should remain local, then create empty directories for the bind mounts:
# Example for user 'admin'
# Copy folders that stay local
cp -a /mnt/myshare/_data/admin/cache /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/admin/
cp -a /mnt/myshare/_data/admin/files_encryption /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/admin/
# Create empty directories for bind mounts
mkdir -p /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/admin/{files,files_trashbin,files_versions,uploads}
# Set ownership
chown -R www-data:www-data /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/adminRepeat for each user. For usernames with spaces:
cp -a "/mnt/myshare/_data/Hans Werner/cache" "/var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/Hans Werner/"
cp -a "/mnt/myshare/_data/Hans Werner/files_encryption" "/var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/Hans Werner/"
mkdir -p "/var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/Hans Werner"/{files,files_trashbin,files_versions,uploads}
chown -R www-data:www-data "/var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/Hans Werner"D. Update compose.yml
Edit ~/containers/nextcloud/compose.yml and change:
NEXTCLOUD_DATADIR: /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/E. Set up bind mounts and verification service
Follow Step 10 to add fstab entries for each user, binding their Storage Box folders into the local data directory.
Follow Step 11 to create nextcloud-mounts-check.service and update Docker dependencies.
F. Start Nextcloud
systemctl daemon-reload
mount -a
cd ~/containers/nextcloud
docker compose up -dG. Verify everything works
- Log into Nextcloud and check that files are accessible
- Verify mounts are active:
mount | grep nextcloud_aio_nextcloud_data - Check encryption keys are local:
ls -la /var/lib/docker/volumes/nextcloud_aio_nextcloud_data/_data/files_encryption/
H. Clean up old data on Storage Box (optional)
Once you've confirmed everything works, you can remove the now-redundant files from the Storage Box:
# Remove global files that are now local
rm -rf /mnt/myshare/_data/files_encryption
rm -rf /mnt/myshare/_data/appdata_*
rm -f /mnt/myshare/_data/.htaccess
rm -f /mnt/myshare/_data/.ncdata
# For each user, remove the folders that are now local
rm -rf /mnt/myshare/_data/admin/cache
rm -rf /mnt/myshare/_data/admin/files_encryption
# Repeat for other usersKeep the user files/, files_trashbin/, files_versions/, and uploads/ folders — they contain your actual file data and are now bind-mounted.
After migration
Your encryption keys now live exclusively on the LUKS-encrypted VPS disk. Even if someone gains access to your Storage Box, they cannot decrypt your files.
Conclusion
Congratulations! You now have your own private Nextcloud with:
- Proper encryption key separation — keys on LUKS-encrypted local disk, encrypted files on expandable Storage Box
- 1TB+ of expandable storage — increase Storage Box size anytime without rebuilding
- Full control — your data, your server, your rules
Have fun exploring your Nextcloud — set up calendars, contacts, notes, video calls, and all the other features that make it a genuine alternative to Big Tech cloud services.
The setup requires a bit more administration than a simple "point everything at the Storage Box" approach, but the security improvement is significant. Your encryption keys never leave your control.
Enjoy your private cloud!