Published on

Installing NextCloud on Debian System

cover

Introduction

The motivation behind this document is to address the situation where one wants to install a Nextcloud service for file storage.

Nextcloud is a web application that requires a database and an infrastructure commonly referred to as LAMP (Linux, Apache, MySQL, and PHP).

The main issue I observe in people who encounter problems during installation is that they are unfamiliar with the infrastructure and the elements that support the application. In my opinion, they have taken on a task that is beyond their skill set and should have left it to someone with sufficient knowledge.

Even though it may not seem that way or might not be intended, the reality is that Nextcloud is a very complex application to install and maintain. It requires advanced knowledge in various areas of operating systems and services (databases, web servers, PHP software, storage, networks, DNS, permissions, security, SSL, etc.). While the documentation and community support are extensive, I believe they are insufficient and fail to cover many aspects that are often left ambiguous.

While it's plausible that someone may want to learn and "tinker" with the system, in this case, especially in a professional environment, it's best to leave the task to someone with the necessary expertise in the aforementioned areas. I am not going to attempt to "make it easy" for someone without sufficient knowledge at the cost of sacrificing stability and security.

This document will not delve into explaining basic concepts of the involved areas, as it assumes that foundation is already understood. Instead, it will emphasize the necessary elements for basic functionality, along with some specific issues that might arise when getting the application up and running. Therefore, this document is intended for individuals with Linux knowledge who know how to use the command line to download files, modify files, set permissions, restart services, etc.

Specifically, and in a basic form, we will initially focus on installing Nextcloud on a direct backend infrastructure.

Prerequisites

The optimal and most secure performance is achieved in a Linux environment. Therefore, for the most basic setup, free from issues that might unnecessarily complicate matters, we will use a Debian Bookworm distribution, which contains all the necessary components for everything to work correctly without excessive headaches.

As an additional note, any variant of a Debian installation without a graphical environment (which is not needed at all) should work. This includes variants like an LXC, LXD container, Docker, VirtualBox, VMWare, QEMU, or WSL2, which are at the discretion of each user.

In any case, it is essential to highlight that whatever environment is chosen, one should have sufficient knowledge to diagnose potential issues and modify it to adapt to particular needs or future updates. It should also have enough flexibility to scale without having to start from a backup and restore from scratch or similar.

Preparing the Database

If it is not already installed, we need to install the database service. I choose MariaDB (although others like PostgreSQL are supported). There is a prerequisite: configure MariaDB to use InnoDB instead of MyISAM. To do this, create a file in /etc/mysql/conf.d named innodb.cnf (although the name can be anything as long as it has the .cnf extension):

[mysqld]
innodb_file_per_table=1
open_files_limit=100000
innodb_buffer_pool_size=1G
innodb_io_capacity=4000

After making these changes, the service needs to be restarted. Then, you must create a database and a user who has access to only that database:

MySQL [(none)]> create database nextcloud;
MySQL [(none)]> grant all on nextcloud.* to 'nextcloud'@'localhost' identified by 'nextcloud';
MySQL [(none)]> flush privileges;
MySQL [(none)]> exit

For the next configuration step, we need to take into account the database server, the database name, the user, and their password.

Preparing the PHP Environment

Nextcloud requires a set of mandatory PHP modules, as well as some optional ones. Additionally, the configuration of php-cli must also be considered. In summary, the minimum modules for a basic installation are as follows:

php-apcu php-bz2 php-common php-curl php-fpm php-gd php-intl php-mbstring php-mysql php-xml php-zip

Additionally, the curl package must be installed, as it is not installed as a dependency of php-curl. All of these packages can be included in a single apt install command, which will install all the necessary dependencies.

Preparing Other PHP Configuration Files

Without going into too much detail, within the PHP configuration directory, the following modifications must be made:

  • File: /cli/php.ini: Set opcache.enable_cli=1
  • File: /fpm/php.ini: Set opcache.enable=1

Preparing php-fpm

Within the PHP configuration, there's the php-fpm interpreter, which has become essential in recent years for connecting with the web server. The configuration, considering that the PHP version in Bookworm is 8.2, is located in /etc/php/8.2/fpm/. In this path, within pool.d, you must modify the www.conf file. In this file, locate three sections to make the following changes (by removing or adding comments with ;):

;listen = /run/php/php8.2-fpm.sock
listen = 9082

...

;env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
;env[TMP] = /tmp

...

;php_admin_value[error_log] = /var/log/fpm-php.www.log
;php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 512M

For the last parameter (memory_limit), the minimum value for Nextcloud is 128M, but to allow automatic updates (and perhaps other features) to function, it should be increased to at least 512M.

After making these modifications, you need to restart the php-fpm service.

Preparing the Web Server

We will review the configuration for both Apache and Nginx. You should choose one of the two. Personally, I prefer Nginx. In both cases, we will create a configuration for a virtual host named nextcloud.example.localnet, which will listen on port 80 (you can later change the name and port as needed).

If you don't have a DNS server that centralizes the name, you will need to modify the /etc/hosts file to include a line with the chosen name.

Lastly, we choose /var/www/vhosts/nextcloud as the root directory for Nextcloud. We create the directory and set the user www-data as the owner:

mkdir -p /var/www/vhosts/nextcloud
chown www-data /var/www/vhosts/nextcloud

Apache

Nextcloud includes an .htaccess file with the basic configuration for it to work. For optimal performance, you need to install and enable the proxy_fcgi module:

apt install mod_proxy_fcgi

This will install all necessary dependencies. Since php-fpm is installed, it will generate a configuration file at conf.d/php8.2-fpm.conf for php-fpm. This file needs to be adjusted for the changes made in php-fpm:

...
    <FilesMatch ".+\.ph(?:ar|p|tml)$">
        SetHandler "proxy:fcgi://localhost:9082"
    </FilesMatch>
...

Then, create the virtual host with the chosen name and the corresponding DocumentRoot. Since the PHP configuration is global, it will be used for all PHP files. The configuration would look something like this:

<VirtualHost *:80>
  DocumentRoot /var/www/vhosts/nextcloud/
  ServerName  nextcloud.example.localnet

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

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

Don't forget to use a2enmod, a2ensite, and a2enconf to activate what you’ve created, and finally, restart the service.

Nginx

Initially, configuring Nginx is complex, but thanks to a dedicated tool, this task becomes very simple.

Installing the vhost generator for Nginx

Once Nginx is installed in the normal directory (/etc/nginx), clone the repository for the dedicated tool from this path (you will need to install the git package if it's not already installed). In the generator’s directory (/etc/nginx/vhost-generator), the first step is to duplicate the defaults.inc_dist file as defaults.inc:

# cd /etc/nginx
# git clone https://github.com/omgslinux/nginx-vhost-generator vhost-generator
# cd vhost-generator
# cp defaults.inc_dist defaults.inc

For our example, the defaults.inc file can have the following content:

SUFFIX="example.localnet"

# Usual setup for letsencrypt
#SSL_CERTIFICATE="/etc/letsencrypt/live/${VHOST}.${SUFFIX}/fullchain.pem"
#SSL_CERTIFICATE_KEY="/etc/letsencrypt/live/${VHOST}.${SUFFIX}/privkey.pem"

getLogDirFormat()
{
    # You can set a custom format per vhost, so let's check that
    if [[ -z ${LOGDIRFORMAT} ]];then
        # Set the format for log directory, called after vhost parsing
        LOGDIRFORMAT="${SERVER}.${SUFFIX}"
        # Another chance
        #LOGDIRFORMAT="${SUFFIX}/${SERVER}"
    fi
}

# Settings for several general options, to be overridden when necessary
CLIENT_MAX_BODY_SIZE="512M" #   client_max_body_size 512M;
CLIENT_BODY_TIMEOUT="300s"  #   client_body_timeout 300s;
FASTCGI_BUFFERS="64 4K"     #   fastcgi_buffers 64 4K;

Now, create a directory for our vhost definition. We will call it nextcloud. Inside this directory, copy the Nextcloud example for further modification.

mkdir nextcloud
cp _examples/nextcloud_example.inc nextcloud/nextcloud.inc
vim.tiny nextcloud/nextcloud.inc

We modify this file to create the vhost, leaving it like this (adapting the vhost):

# Template for a nextcloud in a self hosted vhost
VHOST_TYPE="nextcloud"

SERVER="nextcloud" # Vhost used name
SUFFIX="example.localnet" # Suffix to add to ${SERVER} to have a FQDN
DOCROOT="/var/www/vhosts/nextcloud" # The vhost document root
HTTP_PORT="80" # Port for http request
HTTP_REDIRECT="" # Set any value to force redirect http to https
#HTTPS_PORT="443" # Port for https

# ONLY if nextcloud is installed in a subdirectory of an existing vhost,
# where probably DOCROOT above should be the root value of the existing.
NEXTCLOUD_DIR=""

# Usual setup for letsencrypt
#SSL_CERTIFICATE="/etc/letsencrypt/live/${SERVER}.${SUFFIX}/fullchain.pem"
#SSL_CERTIFICATE_KEY="/etc/letsencrypt/live/${SERVER}.${SUFFIX}/privkey.pem"

# The specific lines for CA certificate and client verification level in config file.
#SSL_CLIENT_CERTIFICATE="/etc/ssl/private/CA.pem"
#SSL_VERIFY_CLIENT="optional"

# Set to any value for using fastcgi and client certificates.
# This will allow your app to check the client certificate validation process
SSLCLIENT_FASTCGI=""

It is possible that the default vhost provided by the nginx installation might interfere, so we can remove all enabled vhosts. Then, we generate the vhost file:

rm /etc/nginx/sites-enabled/*
./mkvhost.sh nextcloud

Check that the nextcloud.conf file in /etc/nginx/conf.d points to the correct php_upstream. If it will always use the global php-fpm, it's best to set it to point there:

# Set the `immutable` cache control options only for assets with a cache busting `v` argument
map $arg_v $asset_immutable {
    "" "";
    default ", immutable";
}

upstream php_nextcloud {
	server 127.0.0.1:9082;
}

map $host $php_nextcloud {
	#default php_nextcloud;
	default php8;
}

We verify with the nginx -t command that everything is correct. If it is, we restart the nginx service, and it's ready to go.

Installing Nextcloud

Preparation

Unlike most distribution software (though web applications tend to be an exception), it's advisable to download the application directly from the Nextcloud website. The URL where all versions are available is https://download.nextcloud.com/server/releases/. It is recommended to choose the latest stable version with at least a minor version of 4 (currently version 28, as version 29 still has some bugs). Once the zip or tar file is downloaded and extracted, there should be an index.php file in /var/www/vhosts/nextcloud.

It's also important to understand that Nextcloud consists of two distinct parts:

  1. The application directory structure, which is located within the web server (what we downloaded from the website).
  2. The data directory, which is meant to store data (so it needs sufficient storage capacity). Although there is a data directory within the web structure, it is not recommended to use this directory; instead, it's better to have a separate structure outside the web service. I usually use /var/lib/nextcloud.

In any case, we must grant full permissions to the www-data user for both directories.

There are two ways to install it: the most common is via the web (where you can visually diagnose any issues), or via the terminal using occ, for which I've created a wrapper to make the task easier.

Web Installation

Following the steps, if we go to the browser and use the URL set up when the vhost was created on the web server (in our case, http://nextcloud.example.localnet), the Nextcloud installation screen should appear. Here, it asks for the creation of an admin user for Nextcloud, the desired password, the data directory, and the database connection details. It's possible you may see an additional message if a PHP module is missing or if a directory lacks write permissions, but under normal circumstances, after filling in the details, the installation program should proceed to set up the database. Once complete, it will redirect to the login screen, allowing you to log in with the credentials provided.

Installation via Terminal (using occ)

We choose or create a secure directory (due to the need to handle users and passwords) where we create two files that will contain the necessary commands to automate the installation of Nextcloud without using the web interface.

First, the install_nextcloud_inc.sh file:

# DocumentRoot where the nextcloud vhost is located
DOCROOT="/var/www/vhosts/nextcloud"

# Database access configuration
DBTYPE="mysql"
DBHOST="localhost"
DBUSER="nextcloud"
DBPASS=$DBUSER
DBNAME="nextcloud"

# Nextcloud data directory
DATADIR="/var/lib/nextcloud"

# Nextcloud admin user
ADMINUSER="admin"
ADMINPW="AdminPass"

Obviously, we use the values we want for our installation.

Then, the install_nextcloud.sh file:

#!/bin/bash

. install_nextcloud_inc.sh

cd $DOCROOT
OPTIONS="maintenance:install -vvv --data-dir=$DATADIR \
--database=$DBTYPE --database-host=$DBHOST --database-name=$DBNAME \
--database-user=$DBUSER --database-pass=$DBPASS \
--admin-user=$ADMINUSER --admin-pass=$ADMINPW"

if [[ -z $(which sudo) ]];then
   OLDSHELL=$(getent passwd www-data | awk -F: '{print $7}')
   usermod www-data -s /bin/bash
   NEWSHELL=$(getent passwd www-data | awk -F: '{print $7}')
   su www-data -c "php occ $OPTIONS"
   usermod www-data -s $OLDSHELL
else
   sudo -u www-data php occ $OPTIONS
fi

From the directory, we give execution permission and run ./install_nextcloud.sh, and everything should work correctly.

What's Next?

There are many things you can do (always keeping basic use in mind). The most recommended thing after finishing the installation is to go to the administration page and check the "System Information" section to see if there are any warnings about problems that can be fixed. You can also delve deeper into the knowledge of Nextcloud, and there are things that are essential to know when managing a Nextcloud installation.

The Configuration File

One important aspect for a sysadmin, aside from the distinct parts, is the config directory located in the root of Nextcloud's web directory. In this directory, you'll find the global configuration file config.php, where parameters are stored. This file is modified multiple times, either transparently or through actions that take place in the application, so it's a good idea to keep a backup copy of it.

The file contains the PHP variable $CONFIG, which is an associative array of values read with each request. Although we can modify it manually as needed, if we make a syntax error or mistype a key or its value, we may encounter application errors that prevent its proper functioning. Therefore, it's wise to save the file in such cases but stay in edit mode to be able to undo changes and revert to the previous state while reviewing what went wrong. Another option is to investigate if the same result can be achieved via an occ command.

The Data Directory

The data directory is more than just a data storage area. In this directory, Nextcloud stores not only a directory structure for user data but also application logs that help diagnose errors (basically, the "Log" section in the administration menu). It also stores various information during update processes. Therefore, in addition to the provision mentioned at the beginning, it's a good idea to monitor this space to ensure it doesn't fill up and check its contents.

The occ Command

Perhaps the main component Nextcloud uses for many operations is a PHP script located in the root of the application called occ. This script can do many things, and it cannot be run as root. Instead, it should be invoked from the Nextcloud web directory as sudo -u www-data php occ <command>. Although for simplicity we might say to execute occ <command>, it actually means you should execute the full command. The documentation for the commands implemented in occ is varied and changes depending on the version, so it's essential to consult the documentation.

Running the bare occ command will show you the available commands. There are also compound commands consisting of two or more parts separated by ":", in which case, typing just the first part will show you the possible commands that begin that way.

Periodic Tasks

There's also a script in the web directory called cron.php. It's recommended to schedule its execution via cron, using the www-data user's crontab. Recent changes notify you if tasks are executed during load hours and/or too frequently. In production, I schedule it to run every 20 minutes for safety and to ensure it doesn't place too much load on the system.

Memory Optimization

This part is crucial, and basically, if there are no warnings on the system information page, performance is optimal. In case there are warnings, the best source of information is the documentation, which explains how to manage memory with apcu, memcached, and redis-server.

Updating Nextcloud

If Nextcloud has been installed following the instructions in this documentation, there should be no issues when updating. There are two ways to update:

Using the web interface:
From the administration page, when a new version is detected, the option to update is offered. By clicking the button, the updater opens and displays the steps it follows to proceed with the update. The update process includes several steps, such as file verification, downloading the new version, etc. This process should take no more than a few minutes. While the update is in progress, Nextcloud enters maintenance mode (i.e., it prevents any user from accessing and performing actions, showing a screen indicating to wait until the update is complete). Once the new version is downloaded and files are prepared, there is an option to complete the update either through the web interface or from the terminal. We'll choose the web interface, and when the process finishes without errors, the system should automatically redirect to the application, with maintenance mode disabled.

Using the terminal:
At this point, there are two possibilities:

  1. The application is running, and the download has not started yet. This would essentially be the same as through the web interface but done via a command (without occ). The command would be:

    sudo -u www-data php --define apc.enable_cli=1 ./updater/updater.phar
    

    When the process finishes, and maintenance mode is disabled, you can verify through the web interface that the application is updated.

  2. The update process and the download of the new version have been completed, but for some reason, the update has stopped. Unless an error indicates otherwise, use occ upgrade to complete the update, responding to any prompts that may appear. Finally, you will need to disable maintenance mode, which can be done with occ maintenance:mode --off. You can also edit the config.php file and set the maintenance key to false (without quotes). It is also possible that you will be prompted to use the occ maintenance:repair command as a troubleshooting mechanism.

Using a Reverse Proxy

Let's address some more advanced topics. In our current backend setup, Nextcloud does not (and should not) have a public IP address, meaning that as it stands, it's for internal use only by those with direct access to the private IP where it’s installed.

In the real world, there is typically what is called a "frontend," which has more public access and implements security measures to prevent direct access to the real server (located in what is called a DMZ, a protected zone from public access). This frontend serves as the only entry point from the outside to the application, allowing access only to the web request port.

Besides all the firewall concerns that we won’t discuss here, a key element in such setups is securing communication through encryption via an SSL connection using the HTTPS protocol.

We won't go into detail about creating a certificate (that’s usually handled by the organization in charge), but it’s worth mentioning that many sites use a Let's Encrypt certificate through the Certbot tool. Let’s Encrypt provides certificates whose CA (Certificate Authority) is recognized by most browsers, and they are valid for 90 days, after which they must be renewed. For our example, we will assume that this certificate is already in place.

Additionally, the frontend must have a DNS name through which the service can be accessed (usually, multiple names are used—one for each service). This way, the client makes a request to the frontend, which transparently communicates with the backend and serves the content as if the request were made directly to the backend. From a content perspective, this setup is called a reverse proxy.

In this scenario, we will configure an Nginx server to act as a reverse proxy. As we did with the backend, we clone the vhost generator repository in the Nginx directory. After cloning, we duplicate the defaults.inc_dist as defaults.inc, and this file can be left exactly as it was for the backend.

Let’s assume the frontend will be nextcloud.example.org. We create a directory named nextcloud and copy the proxy example using:

cp _examples/proxy_example.inc nextcloud/nextcloud.inc

Next, we edit this file:

# Template for a proxy. Use this when you have a frontend and just need to use a proxy_pass to an upstream backend
VHOST_TYPE="proxy"

SERVER="nextcloud" # Vhost used name
SUFFIX="example.org" # Suffix to add to ${SERVER} to have a FQDN
DOCROOT="/var/www/vhosts/${SERVER}" # The vhost document root
HTTP_PORT="80" # Port for http request
HTTP_REDIRECT="1" # Set any value to force redirect http to https
HTTPS_PORT="443" # Port for https

# Usual setup for letsencrypt
SSL_CERTIFICATE="/etc/letsencrypt/live/${SERVER}.${SUFFIX}/fullchain.pem"
SSL_CERTIFICATE_KEY="/etc/letsencrypt/live/${SERVER}.${SUFFIX}/privkey.pem"

# The specific lines for CA certificate and client verification level in config file.
# SSL_CLIENT_CERTIFICATE="/etc/ssl/private/CA.pem"
# SSL_VERIFY_CLIENT="optional"

# Set to any value for using fastcgi and client certificates.
# This will allow your app to check the client certificate validation process
SSLCLIENT_FASTCGI=""

# Parameter for proxy_pass in / location. ONLY IN FRONTEND
PROXY_PASS="http://nextcloud.example.localnet"

# Settings for several general options, to be overriden when necessary
CLIENT_MAX_BODY_SIZE="512M" #   client_max_body_size 512M;
CLIENT_BODY_TIMEOUT="300s"  #   client_body_timeout 300s;
FASTCGI_BUFFERS="64 4K"     #   fastcgi_buffers 64 4K;

If you have a different certificate and key, make sure to set the correct paths in the SSL_CERTIFICATE and SSL_CERTIFICATE_KEY variables. We also need to ensure that the frontend can correctly resolve the name nextcloud.example.localnet. The simplest solution, if there is no DNS server handling this, is to add the corresponding line to the /etc/hosts file.

Generate the vhost with ./mkvhost nextcloud, verify there are no errors using nginx -t, and restart the Nginx service. At this point, we should have a connection to the backend from a client through the frontend.

Trusted Domains and Proxies

However, despite having connectivity, you will most likely encounter an application error indicating that the proxy is not trusted, or that the domain being accessed is not trusted. This is due to security mechanisms that Nextcloud developers have implemented, but it's easy to configure everything to work as expected.

First, you will usually see an on-screen message giving a hint about which IP or name is problematic and how to solve the issue. This is done by manually adding the problematic service to the configuration file within the $CONFIG variable.

Nextcloud needs to have all the domain names that a client may use to access it configured. To do this, you’ll add these names under the trusted_domains key, which would look something like this:

'trusted_domains' =>
array (
  0 => 'nextcloud.example.localnet',
  1 => 'nextcloud.example.org',
  2 => '10.38.50.103', // The reverse proxy IP
),

Although you can add the IP of an intermediate proxy to trusted_domains, the trusted_proxies key is more specific. So, while the above setup is valid, it’s more correct to configure it like this:

'trusted_domains' =>
array (
  0 => 'nextcloud.example.localnet',
  1 => 'nextcloud.example.org',
),
'trusted_proxies' =>
array (
  0 => '10.38.50.103',
),

Finally, the public URL clients will use should be set in the overwrite.cli.url key with the appropriate value:

'overwrite.cli.url' => 'https://nextcloud.example.org',