NextCloud on OpenBSD

NextCloud is an awesome, secure and private alternative for proprietary platforms like Dropbox and Google Drive. Installing NextCloud can be achieved easily with pkg_add nextcloud - but I'd like to do it manually to benefit performance and stability.

General

Setting up NextCloud manually offers some room for improvements:

  • PostgreSQL as the database engine. This benefits a multi-user instance when it comes to performance.
  • PHP 7.0 for even more increased performance - it is way faster than PHP 5.6.
  • Caching file-locking through APCu and Redis.

Caveats:

  • PostgreSQL is more performant for a multi user setup. It is possible to switch database engines afterwards.
  • FFS might not be the best fit for bulky data (>8TB) or when high IOPS are a requirement.
  • OpenBSD doesn't support RAID stacking (eg, RAID1 and FDE) - but NextCloud offers server- and user level encryption.

Coffee is needed

Step 1: Preparations

This guide should work with the latest -stable and the most recent -current. You might prefer a clean and fresh OpenBSD installation to prevent existing configurations from interfering. Without further ado, brew yourself a cup of coffee, disable any distractions and let's go!

We're going to install NextCloud on the subdomain nc.h3artbl33d.nl and are using regular user johndoe. Take note of this and replace these values in commands and configuration files.

1.1: Login and set some defaults

After having logged in to the target machine - that is going to run your NextCloud machine - we'll first set some sane defaults.

  1. Enable doas.
# echo 'permit johndoe' >> /etc/doas.conf
  1. Edit /etc/ssh/sshd_config and set these values - unless you explicitly need them:

    LogLevel VERBOSE
    PermitRootLogin no
    MaxAuthTries 2
    MaxSessions 2
    AllowAgentForwarding no
    AllowTcpForwarding no
    TCPKeepAlive no
    Compression no
    ClientAliveInterval 2
    ClientAliveCountMax 2
  2. Download the pf ruleset I've prepared for you and edit it (specifically the interface and remove the IPv6 rules if you are not using IPv6). When you are done, move it to the default config and check the syntax.

    $ ftp https://h3artbl33d.nl/pf-nc.txt
    $ doas mv pf-nc.txt /etc/pf.conf
    $ doas pfctl -nf /etc/pf.conf

1.2: Download/install the prerequisites

We'll need some files further on, let's make sure we have everything ready.

  1. Install PostgreSQL server:

    $ doas pkg_add postgresql-server
  2. Install PHP and some modules. If you are asked which PHP-version to install, answer PHP 7 each time.

    $ doas pkg_add php php-curl php-gd php-intl php-mcrypt php-pdo_pgsql php-xml php-zip
  3. Enable the PHP modules.

    $ doas cp /etc/php-7.0.sample/* /etc/php-7.0/
  4. Automatically start these services. Do not start these services yet.

    $ doas rcctl enable postgresql-server php70_fpm redis
  5. Install a file so the webserver can resolve DNS queries from inside its chroot.

    $ doas mkdir /var/www/etc && doas cp /etc/resolv.conf /var/www/etc/resolv.conf && doas chown -R www:www /var/www/etc

Keyboard warriors prepare

Step 2: Prepare the webserver

We need to prepare the webserver. Obviously, we're using httpd(8) - see this article if you prefer nginx for some reason. We're going to kick this chapter off with httpd itself.

2.1: Enable and configure httpd

Between 6.3-stable and 6.4-current there have been some syntax changes in httpd.conf. Hence, hereby the valid configuration for both versions.

6.3-stable: use this configuration as a starting point for your /etc/httpd.conf.

server "nc.h3artbl33d.nl" {
        listen on * port 80
        root "/nextcloud"
        location "/.well-known/acme-challenge/*" {
                root { "/acme", strip 2 }
        }
}

6.4-current: use this configuration as a starting point for your /etc/httpd.conf.

server "nc.h3artbl33d.nl" {
        listen on * port 80
        root "/nextcloud"
        location "/.well-known/acme-challenge/*" {
                root { "/acme" }
                request strip 2
        }
}

Check whether the configuration is deemed valid with doas httpd -n. Moving on to preparing acme-client for the SSL certificates, courtesy of Let's Encrypt. Edit /etc/acme-client:

authority letsencrypt {
        api url "https://acme-v01.api.letsencrypt.org/directory"
        account key "/etc/acme/letsencrypt-privkey.pem"
}
authority letsencrypt-staging {
        api url "https://acme-staging.api.letsencrypt.org/directory"
        account key "/etc/acme/letsencrypt-staging-privkey.pem"
}
domain nc.h3artbl33d.nl {
        alternative names { www.nc.h3artbl33d.nl }
        domain key "/etc/ssl/private/nc.h3artbl33d.nl.key"
        domain certificate "/etc/ssl/nc.h3artbl33d.nl.crt"
        domain full chain certificate "/etc/ssl/nc.h3artbl33d.nl.pem"
        sign with letsencrypt
}

And create the corresponding directories:

$ doas mkdir -p -m 700 /etc/acme
$ doas mkdir -p -m 700 /etc/ssl/acme/private
$ doas mkdir -p -m 755 /var/www/acme

Time to fetch the certificates!

$ doas rcctl enable httpd && doas rcctl start httpd && doas acme-client -vAD nc.h3artbl33d.nl

If that went successful, grab the OCSP stapling file.

$ doas ocspcheck -N -o /etc/ssl/nc.h3artbl33d.nl.ocsp.pem /etc/ssl/nc.h3artbl33d.nl.pem

Edit the crontab to automatically renew the certificates and stapling file. Append the following in the crontab (doas crontab -e).

0 0 * * * acme-client nc.h3artbl33d.nl && rcctl reload httpd
0 * * * * ocspcheck -N -o /etc/ssl/nc.h3artbl33d.nl.ocsp.pem /etc/ssl/nc.h3artbl33d.nl.pem && rcctl reload httpd

2.2: Configuring httpd further

6.3-stable: edit /etc/httpd.conf with these values. Do not restart httpd afterwards!

server "nc.h3artbl33d.nl" {
        listen on * tls port 443
        hsts {
                preload
                subdomains
        }
        root "/nextcloud"
        directory index "index.php"
        tls {
                certificate "/etc/ssl/nc.h3artbl33d.nl.pem"
                key "/etc/ssl/private/nc.h3artbl33d.nl.key"
                ocsp "/etc/ssl/nc.h3artbl33d.nl.ocsp.pem"
                ciphers "ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA"
                dhe "auto"
                ecdhe "P-384"
        }
        connection max request body 537919488

        location "/.well-known/acme-challenge/*" {
                root { "/acme", strip 2 }
        }

        # First deny access to the specified files
        location "/db_structure.xml" { block }
        location "/.ht*"             { block }
        location "/README"           { block }
        location "/data*"            { block }
        location "/config*"          { block }
                location "/build*"              { block }
                location "/tests*"              { block }
                location "/config*"             { block }
                location "/lib*"                { block }
                location "/3rdparty*"           { block }
                location "/templates*"          { block }
                location "/data*"               { block }
                location "/.ht*"                { block }
                location "/.user*"              { block }
                location "/autotest*"           { block }
                location "/occ*"                { block }
                location "/issue*"              { block }
                location "/indie*"              { block }
                location "/db_*"                { block }
                location "/console*"            { block }

        location "/*.php*" {
                fastcgi socket "/run/php-fpm.sock"
        }
}

server "nc.h3artbl33d.nl" {
        listen on * port 80
        block return 301 "https://nc.h3artbl33d.nl$REQUEST_URI"
}

If using 6.4-current, replace the location "/.well-known/acme-challenge/*" block to

        location "/.well-known/acme-challenge/*" {
                root { "/acme" }
                request strip 2
        }

Prepare for some hard work

Step 3: PHP kung fu and SQL jiujitsu

In the first step, we've installed PostgreSQL-server and PHP along with some extensions. We've met the preconditions, time to get this all up and running. This is the most intense step, so you might want to grab another cup of coffee. Or tea, if that is more your thing.

3.1: Kicking PostgreSQL online

Since this is the first time we're using PostgreSQL on this machine, we'll need to initialize the database.

$ doas su - _postgresql
$ mkdir /var/postgresql/data
$ initdb -D /var/postgresql/data -U postgres -A md5 -W

Switch back to our regular user, johndoe, by typing exit twice. Now, let's start the database by issuing a single command.

$ doas rcctl enable postgresql && doas rcctl start postgresql

If you expect to house a busy NextCloud instance, you might want to do some configuration tweaking according to the instructions in cat /usr/local/share/doc/pkg-readmes/postgresql-server-{ver}.

3.2: Compiling PHP modules

Like mentioned earlier, for caching and improved performance, we are going to use the APCu and Redis extensions for PHP. We need to compile these ourself - which isn't as hard as it might sound. First, let's take care of some dependencies. When asked which autoconf to install, select the most recent version and write the version number down, minus the patch level. Eg, if you are installing autoconf-2.69p2, write down 2.69. We'll need that later on.

$ doas pkg_add install autoconf
$ doas ln -s /usr/local/bin/php-7.0 /usr/local/bin/php
$ doas ln -s /usr/local/bin/phpize-7.0 /usr/local/bin/phpize
$ doas ln -s /usr/local/bin/php-config-7.0 /usr/local/bin/php-config

Fetch both APCu and Redis for PHP.

$ ftp https://pecl.php.net/get/{apcu-5.1.12.tgz,redis-4.1.1.tgz}
$ tar zxf apcu-5.1.12.tgz && tar zxf redis-4.1.1.tgz && rm apcu-5.1.12.tgz redis-4.1.1.tgz

We'll start with APCu. Remember writing down the autoconf version? This is the part where you'll need it. In the following example, we're using 2.69.

$ cd apcu-5.1.12
$ export AUTOCONF_VERSION=2.69
$ phpize
$ ./configure
$ doas make install

That was the whole process of compiling and installing APCu. Peanuts, isn't it? Let's repeat these steps for Redis:

$ cd ../redis-4.1.1
$ export AUTOCONF_VERSION=2.69
$ phpize
$ ./configure
$ doas make install

Now we have to change the PHP configuration with some higher limits - as the default only allows uploading of files that are two MB at max. Open /etc/php-7.0.ini and edit these values.

memory_limit = 512M
max_input_time = 180
upload_max_filesize = 512M
post_max_size = 32M
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=1
opcache.save_comments=1

Next, edit /etc/php-fpm.ini.

security.limit_extensions =

And to enable both APCu and Redis, there is one more step. Create /etc/php-7.0/cache.ini with the following content.

extension=redis.so
extension=apcu.so

Almost there, maestro

Step 4: Finally, installing NextCloud

We've come quite a long way. Fortunately, we're almost there! Grab the most current version of NextCloud and extract it to /var/www/nextcloud.

$ ftp https://download.nextcloud.com/server/releases/nextcloud-13.0.6.zip
$ doas unzip -d /var/www nextcloud-13.0.6.zip
$ doas chown -R www:www /var/www/nextcloud

Before we can use NextCloud, we need to create a database to store the data. Replace secret-password with a strong passphrase of your liking.

$ doas su - _postgresql
$ psql -d template1 -U postgres
template1=# CREATE USER nextcloud WITH PASSWORD 'secret-password';
template1=# CREATE DATABASE nextcloud;
template1=# GRANT ALL PRIVILEGES ON DATABASE nextcloud to nextcloud;
template1=# \q

Check whether you are still running as the postgresql user with whoami - if so, just give it an exit to switch back to johndoe. Start the required services. We've set these services earlier to enabled, so with a restart, the services will also start on boot time.

$ doas rcctl start httpd redis php70_fpm postgresql

Fire up your browser and head to your subdomain. You should be greeted there with the installation wizard. Select PostgreSQL as your database, using 127.0.0.1:5432 as the server, nextcloud as the user and the password you've set. Take caution to place the datafolder outside htdocs, eg, in /var/www/ncdata.

After the installation, edit /var/www/nextcloud/config/config.php and add these lines before the closing );.

  'memcache.local' => '\OC\Memcache\APCu',
  'memcache.locking' => '\OC\Memcache\Redis',
  'redis' => array(
    'host' => '127.0.0.1',
    'port' => 6379,
  ),

And last but not least, a cronjob for some regular housekeeping and indexing. By default, it runs one task with each pageload. The preferred way here is to set this via a cronjob.

$ doas crontab -e
*/15    *   *   *   *   /usr/bin/ftp -Vo - https://nc.h3artbl33d.nl/nextcloud/cron.php >/dev/null

Troubleshooting

If something is amiss, there are two logs you should check first and foremost: /var/www/logs/error_log and nextcloud.log in your NextCloud data directory.

Feel free to reach out to me on Mastodon and via e-mail (hello@h3b.nl if you require additional help.

FAQ - Frequently Asked Questions (completely made-up by the author)

Q: The error log mentiones "the Redis server went away"

A: somewhat cryptic error message, I agree. Luckily, the Redis server didn't go on holiday nor went anywhere else, out of your reach. This message merely means that the Redis server is most likely offline. Check with rcctl check redis.

Q: I got a nasty 500/520 error while trying to access NextCloud

A: Most likely, PHP went belly up or it won't start due to an error in the configuration. Check the daemon status with rcctl check php70fpm or try to restart it with rcctl restart php70fpm.

Q: How about security, how safe is my data?

A: OpenBSD is the most secure OS out there. Having said that, nothing is one hundred percent secure. If someone says otherwise, they are flat-out lying to you. This guide follows best practice methods. There is an additional step you could take, that this guide doesn't cover: using encryption from within the NextCloud webinterface. Be cautious, losing either your passphrase or your keys will result in inaccessible data. There is a recovery method, but each user has to allow that in the individual profile. It's disabled by default - being the sanest default.

Furthermore, NextCloud offers a security scan you can use to see whether your instance is safe and sound. You might need to click the refresh icon (on the result page) if you are doing a recheck.

Q: Why go to all this trouble to get NextCloud up and running - when there is a NextCloud package available?

A: As mentioned before, purely for performance reasons. Furthermore, the 6.3-stable package uses PHP 5.6 by default. Having said that, you could always use the package. If you are running -current, almost everything is available through packages - thanks to the awesome Gonzalo - except for APCu.

Q: Are you mad? Spending this much time on a tutorial?

A: Whether I am mad is arguable but off-topic. I like sharing knowledge, writing and am honored that at least someone got this far. Any and all feedback is appreciated but completely optional.

Q: Can you write a tutorial about X?

A: I can always check whether it matches with my interest and whether I deem it worth my time. Feel free to drop me a message.

Etcetera

Sweet ideas

  • Check the available apps - The functionality of NextCloud is extendable by using applications that run within NextCloud. The administrative user has access to an appstore that makes installing additional applications/functionality a breeze. Just try it ;)
  • Customize the branding - Using the Theming application you can setup custom branding - different colors, a custom logo, etc.
  • Enable two factor authentication - NextCloud allows setting up two factor authentication, using the Two Factor TOTP Provider application. You can install and configure it using the administrative user.
  • Setup ransomware protection - If you share your NextCloud instance with others, it might be worth considering using the Ransomware protection application - especially if Windows clients use the instance. This application will prevent uploading of ransomeware'd files and alert both the user and administrator as soon as this is triggered.

Changelog

20180905

[+] Refined the httpd.conf to block additional files and directories (tnx /u/g0nzalo)
[+] Added the crontab instead of the AJAX task runner

20180903

[+] Added a mention of the security scan in the QA section
[+] Added some sweet ideas
[+] Changed the NextCloud version to 13.0.6 in the download step
[+] Changed the PHP module config to copying instead of softlinking (tnx @minWu)
[+] Fixed a missing doas, combined several rcctl commands (tnx @minWu)