Self Hosting Nextcloud for Privacy, the Right Way (September 2024)

Self Hosting Nextcloud for Privacy, the Right Way (September 2024)

Table of Contents

  1. Overview
  2. Step 1: Installation
  3. Step 2: Server Setup
  4. Step 3: Installing Nextcloud (Docker)
  5. Step 4: Setting Up Let’s Encrypt SSL Certificate
  6. Step 5: Finalizing Nextcloud Setup
  7. Errors and Warnings
  8. Useful Docker Commands

Introduction

If you are still using cloud services such as Dropbox or Google Drive for file sync, then this one is for you. Setting up a self-hosted Nextcloud server at home needn’t be difficult or complicated, and it offers complete control over your data, ensuring both privacy and security by keeping everything on your local network. We’ll also do this the right way. That means we’ll be using local disk encryption, and your Nextcloud server will ONLY be accessible at home, not over the internet. If there is a vulnerability, zero-day, or some other attack vector for Nextcloud, then I simply don’t want my home network or personal data exposed or at risk. Also, unlike using a cloud-hosted instance of Nextcloud, self-hosting allows you to maintain complete and total ownership, and privacy of your files. I’ll walk you through the entire process of setting up Nextcloud on your own server, whether that be an old PC or laptop you have lying around, or a dedicated server.

We’ll also set up full disk encryption using LUKS, ensuring that everything stored on the server is encrypted at rest. Using a custom domain name, we’ll also make use of Let’s Encrypt for our SSL certificate, providing encryption in transit. The setup will allow for local syncing when connected to your home network, such as over WiFi, with offline access to files via mobile apps like Strongbox for iPhone or KeepassDX for Android.

If you are new to terms such as RAID, then don’t worry. Just treat this as a learning exercise, and feel free to contact me if you have questions.

Overview

Disclaimer: You need to have a basic knowledge of Linux, as I won’t walk through every step of the initial setup, such as configuring a static IP address or logging in via SSH. If you’re unfamiliar with the basics of Linux, this may not be suitable for you.

Server

Before you get started, you need to decide where to install Ubuntu, which we’ll use to run our Docker image of Nextcloud. This can be a spare laptop, server, or a desktop PC you have lying around. I will say that you want this to be powered on all the time, so if you have a noisy server, leaving it running in your home office is probably going to drive you insane very quickly. I’m fortunate to have a small area in a part of the house that is well insulated and soundproofed, so noise isn’t as much of a factor. I’ve read reports of some people successfully using a Raspberry Pi with NextcloudPi, but I don’t have any experience with that.

I run VMware vSphere 8 thanks to my vExpert license, on my two Supermicro SYS-E300-9D servers, which support 10GbE networking, and are very small and compact. Obviously this is overkill for just a Nextcloud deployment, but I also use this for other virtual machines on my home network.

The choice of server may determine your storage options too, which I’ll discuss next. The reason I use VMware is so I can present an NFS (Network File System) volume from my NAS to VMware, and then create a large virtual disk for my Nextcloud data drive. The reason I do this, and not present the NFS volume directly to my Nextcloud machine, is so I can use LUKS disk encryption which only applies to block devices (HDD, SSD, iSCSI, virtual disks, etc.)

Proxmox

You don’t even need VMware vSphere to do this. A free and open-source alternative is Proxmox VE. Proxmox uses KVM for virtual machines, and LXC for containers. It’s an excellent alternative to running VMware vSphere, and allows you to run other virtual machines or containers in the future.

No matter which option you choose, I’ll assume you are using Ubuntu 22.04, rather than using Proxmox LXC or TrueNAS Scale containers. If you choose the latter, go for it.

Storage

One of the key benefits of self-hosting Nextcloud is you can use your own storage, whether that be an internal SSD or HDD, or something more significant like a NAS. I personally need a lot of storage capacity, so I use a Synology 8-Bay DiskStation DS1823xs, with six 16TB Seagate Ironwolf Pro drives. Configured in a RAID 6 storage pool, this gives me around 58TB of usable storage. However, for a long time I was using a used QNAP TS669 Pro with six 4TB drives (RAID 10).

If you don’t need a large amount of storage capacity like that, then you could even opt for a 2-bay NAS such as the Synology DS223j which costs less than $190 (US). At least with RAID 1 (disk mirroring) you’ll get some redundancy, and it’s a good introduction to using a NAS.

TrueNAS, as mentioned earlier, is another option, where you can repurpose an old PC or server, and build your own NAS. I tried this once, but found that having a PC case crammed full of disks was a cable management nightmare, so I decided just to pull the plug and go with the Synology NAS. However, if this is the direction you want to go in, check out TrueNAS Scale since this also allows you to run containers or virtual machines on the same platform.

1. Ubuntu Installer

By this stage, I will assume you are ready to install Ubuntu 22.04 somewhere, whether that be on a virtual machine or some other device. Here are the initial steps:

  1. Download and mount the Ubuntu 22.04 Server ISO
  2. Update the the new installer
    • If prompted, select ‘Update the the new installer’
  3. Choose Ubuntu Server (minimized)
    • During the installation process, select the Ubuntu Server (minimized) option to reduce unnecessary packages.
  4. Network Configuration
    • Select the network adapter and change from DHCP to Static (Edit IPv4 > Manual)
    • Set to a static IP for your network range (example below)
  5. Enable Disk Encryption (Optional)
    • When you boot the machine, you’ll need to enter this passphrase.
  6. Do NOT Select ‘Nextcloud’ from the ‘Featured Server Snaps’ Screen
    • We’ll be installing Nextcloud manually.

Note: Once Ubuntu Server is installed, you should be able to connect over SSH.

2. Server Setup

Setting the Timezone

First, set your server’s timezone to ensure logs and scheduled tasks run at the correct times. Replace America/New_York with your local timezone:

sudo timedatectl set-timezone America/New_York

Updating the System

Update your package lists and upgrade existing packages:

sudo apt update -y
sudo apt upgrade -y

Install required packages:

sudo apt-get install net-tools vim

Setting Up the Firewall

Usually I’d recommend using UFW (Uncomplicated Firewall), but Docker bypasses UFW rules because it manipulates iptables directly. Since we chose a minimal Ubuntu Server installation, UFW isn’t installed. Instead we’ll use firewalld. Docker integrates with firewalld, as stated on their website, Docker automatically creates a firewalld zone called docker, with target ACCEPT.

  1. Install firewalld
    sudo apt install firewalld
    
  2. Enable and Start firewalld
    sudo systemctl enable firewalld
    sudo systemctl start firewalld
    

Note: We don’t need to open HTTP or HTTPS (SSH is enabled by default) since Docker handles this as previously mentioned.

Installing Docker

Since we’ll be using Docker to run Nextcloud, we’ll install Docker and Docker Compose:

sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release

Add Docker’s official GPG key:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

Set up the stable Docker repository:

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Update package lists again:

sudo apt update

Install Docker Engine and Docker Compose:

sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

Configuring the Hosts File

Add your server’s IP address and hostname to the /etc/hosts file:

sudo vi /etc/hosts

Note: You’ll want to ensure your internal DNS resolves to these hostnames. See the section below on split DNS to handle external / internal name resolution.

Add the following line, replacing with your server’s IP and desired hostname:

192.168.1.146 your_domain_name

Note: Use your own IP address and hostname here.

Enabling Automatic Updates

Set up unattended upgrades to keep your system up to date:

  1. Install unattended-upgrades
    sudo apt install -y unattended-upgrades
    
  2. Enable automatic updates
    sudo dpkg-reconfigure -plow unattended-upgrades
    
  3. Configure /etc/apt/apt.conf.d/50unattended-upgrades This file controls which packages get updated. It should already be pre-configured, but you can review it or modify it if necessary. Just remove the // to uncomment the lines you want.
    sudo vi /etc/apt/apt.conf.d/50unattended-upgrades
    

    Example:

    Unattended-Upgrade::Allowed-Origins {
         "${distro_id}:${distro_codename}";
         "${distro_id}:${distro_codename}-security";
         // Extended Security Maintenance; doesn't necessarily exist for
         // every release and this system may not have it installed, but if
         // available, the policy for updates is such that unattended-upgrades
         // should also install from here by default.
         "${distro_id}ESMApps:${distro_codename}-apps-security";
         "${distro_id}ESM:${distro_codename}-infra-security";
         "${distro_id}:${distro_codename}-updates";
    //      "${distro_id}:${distro_codename}-proposed";
    //      "${distro_id}:${distro_codename}-backports";
    };
    
  4. Check the status of unattended-upgrades
    sudo systemctl status unattended-upgrades
    
  5. Test with --dry-run
    sudo unattended-upgrades --dry-run --debug
    

Adding a Data Disk with LUKS Encryption

At this stage of the setup, we’ve got Ubuntu installed, but I’ll assume the system (OS) disk won’t have enough capacity for our file sync data. I’ll refer to this as the Nextcloud data disk. In my setup, my Nextcloud virtual machine has a 100GB system disk and an additional 4TB disk for Nextcloud data.

As I mentioned earlier, LUKS disk encryption only applies to block devices (HDD, SSD, iSCSI, virtual disks, etc.). My preference is to use a virtual disk, whether you are using Proxmox or VMware, as it simplifies the setup without the need for iSCSI or other complications. If you’re using a physical server, desktop PC, or laptop with physical disks installed, that’s perfectly fine too.

Note: In my example below, my 4TB data disk is presented as /dev/sdb to Ubuntu.

Encrypting the Disk

Note: In the following steps, replace /dev/sdb with the actual device name of your data disk.

  1. Check the device name for your data disk
    sudo fdisk -l
    
  2. Prepare the data disk for LUKS
    sudo cryptsetup luksFormat /dev/sdb
    

    This initializes LUKS encryption on the device /dev/sdb. During this process, you are prompted to enter a passphrase (keep this secure, such as inside your password manager). This passphrase is stored in key slot 0, which is the first slot of the LUKS header. This is important because without at least one valid key, the encrypted volume cannot be unlocked. For automatic unlocking during system boot, we’ll add an additional key to the LUKS header (key slot 1).

  3. Open the encrypted volume:
    sudo cryptsetup luksOpen /dev/sdb datacrypt
    
  4. Format the encrypted partition:
    sudo mkfs.ext4 /dev/mapper/datacrypt
    

Note: I named the encrypted volume datacrypt but you can choose your own volume name here.

Creating a Key File for Automatic Unlocking

  1. Create a key file and add it as an additional key to your LUKS partition:
    sudo dd if=/dev/urandom of=/root/luks.key bs=1024 count=4
    
  2. Set permissions:
    sudo chmod 600 /root/luks.key
    
  3. Add key to LUKS (remember to change /dev/sdb to the correct device path):
    sudo cryptsetup luksAddKey /dev/sdb /root/luks.key
    

Configure /etc/crypttab

  1. Edit the /etc/crypttab file to set up automatic decryption at boot:
    sudo vi /etc/crypttab
    
  2. Add the following line (remember to change /dev/sdb to the correct device path):
    datacrypt /dev/sdb /root/luks.key luks
    

Mounting the Encrypted Disk

  1. Create a mount point and add an entry to /etc/fstab:
    sudo mkdir /data
    
  2. Edit /etc/fstab:
    sudo vi /etc/fstab
    
  3. Add the following line:
    /dev/mapper/datacrypt /data ext4 defaults 0 2
    

Rebooting and Verifying

  1. Reboot the system and verify that the disk is mounted:
    sudo reboot
    
  2. After reboot, you should see the /data mount for the new disk:
    df -h
    

Setting Permissions

  1. Ensure that the web server user (www-data) owns the data directory:
    sudo chown www-data:www-data /data -R
    

3. Installing Nextcloud (Docker)

Creating a Docker Compose File

  1. Create a directory for Nextcloud and navigate to it:
    sudo mkdir -p /data/nextcloud
    
  2. Change to the Nextcloud directory:
    cd /data/nextcloud
    
  3. Create a docker-compose.yml file:
    sudo vi docker-compose.yml
    
  4. Add the following content to docker-compose.yml:
services:
  nextcloud:
    build: .
    container_name: nextcloud
    restart: always
    ports:
      - "443:443"
    volumes:
      - /data/nextcloud/html:/var/www/html
      - /etc/letsencrypt/live/your_domain_name/fullchain.pem:/etc/ssl/certs/ssl-cert.crt:ro
      - /etc/letsencrypt/live/your_domain_name/privkey.pem:/etc/ssl/private/ssl-cert.key:ro
    environment:
      - NEXTCLOUD_DATA_DIR=/var/www/html/data
      - NEXTCLOUD_TRUSTED_DOMAINS=your_domain_name your_ip_address
    depends_on:
      - db
      - redis

  db:
    image: mariadb:10.11
    container_name: nextcloud_db
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=your_db_root_password
      - MYSQL_PASSWORD=your_db_password
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
    volumes:
      - /data/nextcloud/db:/var/lib/mysql

  redis:
    image: redis:alpine
    container_name: nextcloud_redis
    restart: always
    volumes:
      - /data/nextcloud/redis:/data
    command: ["redis-server", "--unixsocket", "/tmp/redis.sock", "--unixsocketperm", "770"]

Replace the following:

Configuration Value
your_domain_name Replace with your own domain name (e.g., nextcloud.domain.com)
your_ip_address Replace with the local IP address of your server
your_db_root_password Replace with a strong root password for MariaDB
your_db_password Replace with a strong password for the Nextcloud database user

Note: We’re using the recommended version of MariaDB 10.11 according to the system requirements

Configure Nextcloud to Use Apache

  1. Create Dockerfile:
    sudo vi /data/nextcloud/Dockerfile
    
  2. Add the following:
    FROM nextcloud
    RUN a2enmod ssl
    COPY nextcloud.conf /etc/apache2/sites-available/nextcloud.conf
    RUN a2ensite nextcloud.conf
    
  3. Create `nextcloud.conf’:
    sudo vi /data/nextcloud/nextcloud.conf
    
  4. Add the following:
<VirtualHost *:443>
    ServerName your_domain_name

    DocumentRoot /var/www/html

    SSLEngine on
    SSLCertificateFile /etc/ssl/certs/ssl-cert.crt
    SSLCertificateKeyFile /etc/ssl/private/ssl-cert.key

    <Directory /var/www/html/>
        AllowOverride All
        Require all granted
        Options FollowSymLinks MultiViews

        <IfModule mod_dav.c>
            Dav off
        </IfModule>
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/nextcloud-error.log
    CustomLog ${APACHE_LOG_DIR}/nextcloud-access.log combined

    <IfModule mod_headers.c>
        Header always set Strict-Transport-Security "max-age=15768000; includeSubDomains; preload"
    </IfModule>
</VirtualHost>

Note: Replace your_domain_name with your actual domain name.

4. Setting Up Let's Encrypt SSL Certificate

We won’t be exposing our Nextcloud server to the internet, however, we do need to use an external domain name if we want to use LetsEncrypt, and take advantage of a trusted SSL certificate. The last thing we want is to deal with self-signed certificate warnings on each of our devices accessing Nextcloud!

Domain Names

I recommend using with Namecheap or Njalla to register a custom domain. Remember, you won’t be using this to access Nextcloud over the internet, but it will be used internally (e.g. nextcloud.yourdomain.com) and to obtain valid SSL certificates for HTTPS from LetsEncrypt.

I also recommend using Cloudflare to manage the DNS (Domain Name System) for your custom domain. In the next section, I guide you through obtaining a Cloudflare API key which we’ll need in order to obtain our SSL certificates without making the server accessible from the internet.

If our server were accessible from the internet, we would have just used certbot to simply create and renew our SSL certificates. As we are doing things a little differently for added security, we’ll use the DNS-01 challenge method which involves proving control over a domain by adding specific DNS TXT records. I use CloudFlare to host the DNS for my domain names, which I’ll use in my example below.

Internal DNS

Let’s say my Nextcloud instance is on nextcloud.psysecure.com. Since I won’t be opening up my firewall to allow this over the internet, I don’t need to add a DNS record in Cloudflare. However, I do want my internal devices to resolve that to an internal IP address. For my home network I use two PiHole servers (virtual machines) but these could also be installed on a Raspberry Pi. They have a ‘Local DNS’ setting, that allows you to add internal DNS records, so nextcloud.psysecure.com would point to the internal IP address of your Nextcloud server (E.g. 192.168.100.146).

Installing Certbot and DNS plugin:

sudo apt update
sudo apt install -y certbot python3-certbot-dns-cloudflare

Obtain CloudFlare API Key

  1. Log into CloudFlare and go to https://dash.cloudflare.com/profile/api-tokens
  2. Choose ‘Use Template’ next to ‘Edit zone DNS’
  3. In Zone Resources > Include > Specific Zone, choose your domain that you’ll use for Nextcloud.
  4. Click ‘Continue to Summary’
  5. Create Token
  6. Save this API token in your password manager, don’t lose it!

Configure DNS API Credentials

  1. Create a credentials file with your DNS API token or keys:
    sudo mkdir /etc/letsencrypt/dns
    sudo vi /etc/letsencrypt/dns/cloudflare.ini
    
  2. Add your API credentials to the file:
    dns_cloudflare_api_token = your_cloudflare_api_token
    
  3. Change permissions:
    sudo chmod 600 /etc/letsencrypt/dns/cloudflare.ini
    

Note: Replace your_cloudflare_api_token with your CloudFlare API key

Request the Certificate

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/dns/cloudflare.ini \
  --dns-cloudflare-propagation-seconds 60 \
  -d your_domain_name

Note: Replace your_domain_name (E.g. nextcloud.domain.com)

Starting the Containers

sudo docker compose up -d

Verify the containers are running:

sudo docker ps

You should see two running containers, for example:

CONTAINER ID   IMAGE                 COMMAND                  CREATED         STATUS                  PORTS                                           NAMES
4678a7c04652   nextcloud-nextcloud   "/entrypoint.sh apac…"   5 seconds ago   Up 2 seconds            80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp   nextcloud
e3158965d2d5   redis:alpine          "docker-entrypoint.s…"   5 seconds ago   Up Less than a second   6379/tcp                                        nextcloud_redis
7f94e9dce0ab   mariadb:10.11         "docker-entrypoint.s…"   5 seconds ago   Up 3 seconds            3306/tcp                                        nextcloud_db

5. Finalizing Nextcloud Setup

Open a web browser and navigate to http://your_server_ip to complete the Nextcloud setup. You will be prompted to create an admin account and configure the database connection. I recommend creating a random username (not admin), similar to my example below. Don’t lose this, store it in your password manager!

  1. Click ‘Storage & database’
  2. Select ‘MySQL/MariaDB’
Configuration Value
Database account nextcloud
Database password The your_db_password you set in docker-compose.yml
Database name nextcloud
Database host db

Click Install!

Note: Access your Nextcloud instance via https://your_domain.com and verify that everything is working correctly.

Enable Redis for Memcache

  1. Edit config.php
    sudo vi /data/nextcloud/html/config/config.php
    
  2. Add the memcache settings to the end of the config
    # Existing config above
      'memcache.local' => '\\OC\\Memcache\\Redis',
      'memcache.locking' => '\\OC\\Memcache\\Redis',
      'redis' => 
      array (
     'host' => '/tmp/redis.sock',
     'port' => 0,
     'timeout' => 1.5,
      ),
    );
    

Errors and Warnings

By this stage you should be logged into Nextcloud. If you click on the user icon (top right) and choose ‘Administration Settings’ you might see a list of errors and warnings. Don’t panic, we’ll fix these!

1. Error: Some files have not passed the integrity check. List of invalid files… Rescan… For more details see the documentation.

This error is related to this issue, since Docker leaves a .lock file behind. We just need to remove it, as follows:

  1. Access the Nextcloud Docker container:
    sudo docker exec -it nextcloud bash
    
  2. Navigate to the Nextcloud installation directory:
    cd /var/www/html
    
  3. Remove the extra file causing the issue:
    rm nextcloud-init-sync.lock
    
  4. Exit the container:
    exit
    
  5. Click ‘Rescan’ in the Nextcloud Administration Settings and this error will go away.

2. Warning: Server has no maintenance window start time configured.

To configure a maintenance window, just edit config.php and add 'maintenance_window_start' => 1, to the end.

sudo vi /data/nextcloud/html/config/config.php

Add this configuration line

  'maintenance_window_start' => 1,

3. Warning: One or more mimetype migrations are available. Occasionally new mimetypes are added to better handle certain file types.

Run occ maintenance:repair --include-expensive as instructed by the warning, inside our Nextcloud container:

sudo docker exec -u www-data -it nextcloud php occ maintenance:repair --include-expensive

Note: You can run other occ commands in the same way, in case you have other warnings that require this command to fix.

4. Warning: Detected some missing optional indices. Occasionally new indices are added (by Nextcloud or installed applications) to improve database performance.

Run occ db:add-missing-indices as instructed by the warning, inside our Nextcloud container:

sudo docker exec -u www-data -it nextcloud php occ db:add-missing-indices

5. Warning: RuntimeException image not found: image:apps/whiteboard.svg webroot: serverroot:/var/www/html

Hopefully they’ll fix this one. At the time of writing this guide, I found this issue which discusses the warning. To fix, they recommend installing the Whiteboard app, then disabling it.

6. Warning: Phone Region Not Set

sudo vi /data/nextcloud/html/config/config.php

Add this to the end:

'default_phone_region' => 'US',

Useful Docker Commands

Verify Docker installation

sudo docker run --rm hello-world

List all containers

sudo docker ps -a

Note: -a shows all containers, otherwise it’ll just show running.

Remove container

sudo docker rm <container_id>

Bring Down All Docker Compose Services

sudo docker compose down

Bring Up and Rebuild All Docker Compose Services

sudo docker compose up --build -d

Check Logs for Specific Container

sudo docker logs nextcloud
sudo docker logs nextcloud_db
sudo docker logs nextcloud_redis

Execute a Command Inside a Running Container

sudo docker exec -it nextcloud bash

Restart a Specific Container

sudo docker restart nextcloud

List all images

sudo docker images

Restart Docker Service

sudo systemctl restart docker