Building a fast, zero trust and secure Nextcloud server
Why?⌗
Using cloud services is great. You can have your data in one place and you don’t have to copy it between devices. But online cloud providers have some drawbacks. Mainly privacy and vendor lock-in. Most online cloud providers have access to your data, as you may want to back up sensitive content, this is a no go for me. Also, you can only choose between the cloud storage sizes offered, with no alternative to switching to cheaper storage down the line without changing the entire cloud provider and its software stack.
Currently no solution is sufficient for my needs.
On site self hosting⌗
One of the, if not the, most popular solutions to this problem is to host your own cloud. The advantage of this is that your data stays on your device and you can upgrade your storage or hardware as you wish. But with that power comes a lot of responsibility: you have to keep your hardware running, update the software, and deal with slow home internet speeds. The upfront costs can also be quite high.
So self-hosting in your own home is less than ideal. What about using a VPS, i.e. a cloud computing provider? Here we have the problem that we don’t have control over our data by default, as the cloud provider can technically inspect the data stored on their equipment.
Self hosting is less than ideal most of the time. We want to use the cloud.
How to do zero trust cloud computing? - defending against a malicous cloud provider⌗
First, there are two ways the data could be seen by the cloud provider in plain text: The hard drive or the RAM. We need to protect both from any external source so that only we have access to the data.
Encrypt RAM, harddrive and any external storage.
Encrypting the harddrive⌗
There is a great blog post that explains how to can enable full disk encryption on a cloud server. This gives us the advantage that our data is encrypted at rest, but our server is running most of the time and needs to handle data as well as the encryption key somewhere in RAM. So how do we encrypt the RAM?
Shifting trust to the silicon - AMD Secure Encrypted Virtualization (SEV)⌗
Basically, AMD SEV exposes the chip’s ability to encrypt the virtual machine’s RAM independently for each VM. This also protects the key from the hypervisor - in our case the cloud provider - so we can compute without the cloud provider knowing what data resides have in RAM.
Encrypt external storage⌗
VPS storage can be quite expensive. Maybe you have some free cloud storage lying around from OneDrive or Google Drive. Or you might have bought a Hetzner storage box or some other external data storage. Most of them don’t give you a way to run code to encrypt the storage yourself, for example. Wouldn’t it be nice if we could somehow use this dumb storage to store our data and run our computing needs on a low storage VPS?
The first problem we need to solve is: how do we mount/connect to the storage? rclone
is a fantastic tool that can interface with most major cloud storage providers, so we can use rclone to mount our storage. However, for my external storage it was much faster to use mount.cifs
to mount the storage using the SMB protocol. The command I use:
mount.cifs -o user=username,pass=passw0rd //user.your-stroragedrive.com/folder /mnt/smb
Now we need to encrypt the storage on our encrypted RAM, harddrive VPS before sending it to storage.
rclone also provides a pipeline step to do just that: encrypt and decrypt storage requests using rclone crypt
.
Another problem we need to solve is that we want to use the storage as if it were a hard drive directly attached to our machine, so that nextcloud thinks it is not an external drive, because I find nextcloud’s handling of external storage lackluster, we can do better! So, to make our I/O operations on our drive 100% reliable, we’ll use the virtual filesystem feature for rclone mount
to buffer all I/O operations.
Making all of it fast - building a cache hierachie.⌗
We utilise different types of storage before our files and images are displayed by Nextloud. The storage hierarchy looks something like this:
external storage –> NVMe SSD –> RAM
To cache external storage I/O operations, we use the rclone vfs cache feature, so we can have a large cache, say 10GB, that can cache our latest photos and videos every time we view our timeline. To cache the files needed for nextcloud, we use reddis to cache those files in RAM. The command I use to mount the rclone vfs cache is as follows:
rclone mount --daemon --vfs-cache-mode=full --allow-other --uid 33 --gid 33 --cache-dir /var/services/rclone/cache --vfs-cache-max-size 10G --vfs-read-ahead 32M --vfs-cache-max-age 72h --vfs-write-back 30s --dir-perms 0750 --file-perms 0644 storagebox_ENC: /var/services/rclone/decryptedStorage
I changed the file and directory permissions so that the www-data
user for the Nextcloud server can access our files.
Making the server secure - WireGuard⌗
Personally, I don’t trust Nextcloud or myself to keep up with all the latest emerging security threats (updating my server). So I don’t want to expose my Nextcloud instance to the whole world wide web. Instead, I want to access it in an allow list fashion. So after enabling auto-update and only allowing public/private key authentication on my server, I also keep my Nextcloud instance behind a WireGuard VPN. Running all our traffic through WireGuard, as most tutorials on the web suggest, is a bit sub-optimal, as we only need to route the traffic to our server through WireGuard. This has the advantage that we can have our WireGuard connection open and always on, without the latency or speed penalty of using a VPN for all our traffic. So how do we do this? I will simply provide you with both the server and client configuration files for such a setup, as the WireGuard installation procedure is well documented online:
[Interface]
Address = 192.168.200.1/24 # the IP of the server
ListenPort = 51820
# Adjust your network interface. enp1s0 in my case
PrivateKey = your_private_key_of_wireguard
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o enp1s0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o enp1s0 -j MASQUERADE
# each device needs its own private/public key pair!
# computer
[Peer]
PublicKey = public_key_of_computer
AllowedIPs = 192.168.200.2/32
# laptop
[Peer]
PublicKey = public_key_of_laptop
AllowedIPs = 192.168.200.3/32
# phone
[Peer]
PublicKey = public_key_of_phone
AllowedIPs = 192.168.200.4/32
[Interface]
PrivateKey = private_key_of_computer
Address = 192.168.200.6/32
[Peer]
PublicKey = public_key_of_server
AllowedIPs = 192.168.200.1/32 # this will route only traffic going to the server through WireGuard
Endpoint = server_ip:51820
PersistentKeepalive = 25
Make sure you set your firewall to only allow inbound traffic to the SSH port and the WireGuard port, and block all HTTP/S traffic coming from the Internet. Only allow HTTP traffic coming from the WireGuard tunnel.
Installing Nextcloud⌗
I will only provide you with my nextcloud.yml
file for my Docker Nextcloud instance as the installation process of Nextcloud is well documented online. Note that I installed aio-imaginary for faster preview image generation and ffmpeg in the Nextcloud image for hardware accelerated video encoding for streaming. But to be honest, these days I just stream my video files without re-encoding them first. The raw internet speed is just faster on my VPS rather than re-encoding the videos on the CPU.
version: "3"
services:
nextcloud:
image: mycloudy/nextcloud-ffmpeg
build: .
container_name: nextcloud
restart: no
volumes:
- /var/services/nextcloud:/var/www/html
- /var/services/rclone/decryptedStorage/nextCloudData:/data
ports:
- 192.168.200.1:80:80
depends_on:
- mysql8
- redis
- aio-imaginary
mysql8:
image: mysql:8
container_name: mysql8
volumes:
- /var/services/mysql:/var/lib/mysql
restart: no
environment:
MYSQL_ROOT_PASSWORD: your_mysql_password
redis:
image: redis:7
container_name: redis
volumes:
- /var/services/redis:/data
restart: no
aio-imaginary:
image: nextcloud/aio-imaginary:20230817_065941-latest
container_name: imaginary
cap_add:
- SYS_NICE
restart: no
environment:
- PORT=9000
ports:
- 9000:9000
command: -concurrency 50 -enable-url-source -log-level debug
App recommendation⌗
I recommend the Nextcloud app memories as it mimics the likes of Apple Photos or Google Photos and is just so much faster and more feature rich than Nextcloud Photos. On iOS, you can also place a PWA link to memories on your homescreen and interact with the site without the Safari UI bars.
I would also recommend an alternative to the Nextcloud app’s auto-upload for uploading images. Because the Nextcloud implementation is easily killed by iOS’s limited background running and does not retry the next time it is opened. Also, the Nextcloud app on iOS is just horribly buggy. So I can recommend PhotoSync for uploading, it can sync in the background and just works.