Setting up Nextcloud with Nginx

In my last post I talked about switching from Dropbox to Nextcloud and some of the advantages and disadvantages. In this post I’ll discuss how I chose to setup my Nextcloud server. For more information beyond the specifics of my installation visit Nextcloud’s documentation.

Contents

Getting Started

I chose to use Ubuntu 16.04 LTS as it’s the flavour I’m most familiar with, it’s also recommended on Nextcloud’s system requirements page. However I went the unsupported route and used my web server of choice, Nginx. Nextcloud only offers support for their recommended choice Apache 2.4 with mod_php. I wont go into details about setting up and securing Ubuntu, that’s beyond the scope of this post. I will however discuss the settings I’ve chosen for Nginx, MariaDB, PHP, and of course Nextcloud itself.

This guide assumes a pretty basic Ubuntu server install with only the standard system utilities and OpenSSH server packages selected during the installation. All commands that require root are prepended with the ‘sudo’ command. Any code block with “[..]” indicates there’s content in that file or section that I’m skipping over for brevity, it’s not something to be included, nor does it indicate anything to be removed.

Prerequisites

First I installed all the prereqs required for my chosen configuration.

$ sudo apt install ffmpeg mariadb-server nginx php7.0 php-apcu php-bz2 php-curl php-gd php-imagick php-intl php-json php-mbstring php-mcrypt php-mysql php-xml php-zip
  • ffmpeg - Used in the generation of thumbnails for movie files
  • mariadb-server - Fork of MySQL used as a drop in replacement for MySQL
  • nginx - The webserver I’ve chosen to use
  • php7.0 - PHP7 is the recommended version of PHP for Nextcloud
  • php-apcu - APCu is an opcache for storing compiled PHP, offering speed improvements.
  • php-bz2 - Recommended module, required for extraction apps
  • php-curl - Recommended module, required for some functionality
  • php-gd - Required module used for generating thumbnails of photos and pictures
  • php-imagick - Optional module used for graphics GD doesn’t support
  • php-intl - Recommended module that fixes sorting of non-ASCII characters
  • php-json - Required module
  • php-mbstring - Required module for multibyte strings
  • php-mcrypt - Recommended module for improving encryption performance
  • php-mysql - Required module for interfacing with a MySQL database
  • php-xml - Required module, SimpleXML
  • php-zip - Required module for reading and writing zip files

The remainder of the required PHP modules came installed with php7.0. However consult Nextcloud’s documentation to be sure everything required is installed.

MariaDB

After installing all the prereqs I immediately ran a script that comes with MariaDB to remove anonymous users, remote root access, and set the root password.

$ sudo mysql_secure_installation

Using the new root password I’d set I logged into MariaDB and created a database and a user for Nextcloud to use.

$ sudo mysql -u root -p
Enter password:
mysql> CREATE DATABASE nextcloud;
mysql> CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'password';
mysql> GRANT ALL PRIVILEGES ON nextcloud . * TO 'nextcloud'@'%';
mysql> FLUSH PRIVILEGES;
mysql> exit

I created a database named ‘nextcloud’ and a user also named ‘nextcloud’ and granted all privileges on the database to the user. I’ll note, technically this doesn’t apply ‘all’ privileges to the user, some privileges like the ability to grant other users privileges must be specified explicitly. Generally you don’t want this privilege applied to users anyway.

Preparing Directories

Two directories were needed for my installation. One for Nextcloud itself, and one for a cache Nginx would use to serve up files faster by skipping sending requests to the PHP process.

$ sudo mkdir -p /srv/public
$ sudo mkdir -p /srv/cache/nginx

The default location for websites in Debian based Linux (and others) is /var/www/. I personally prefer to use /srv for anything I’m serving with the machine, as it makes more logical sense to me. Ultimately it makes no real difference in the end so you may choose either or, just remember to substitute later in this article.

Let’s Encrypt

Using transport encryption is a must on any site, especially on a site that will be hosting your data and using login credentials. Let’s Encrypt offers a free solution for this problem, instead of using a self-signed certificate or paying a company money for the privilege. Additionally their solution can automate renewal making it easy to maintain.

First I added the Electronic Frontier Foundations PPA (Personal Package Archive) repository to my Ubuntu install. Then I updated apt and installed certbot.

$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install certbot 

I decided for this install I would use certbot’s standalone server to get and renew certificates. This would mean starting and stopping Nginx each time certbot checked for renewal, a solution I likely wouldn’t use if I were supporting more than just myself.

$ sudo systemctl stop nginx
$ sudo certbot certonly --standalone -d sub.example.com
$ sudo systemctl start nginx

I then setup a cron job to run weekly. Note the –pre-hook and –post-hook commands, they run before and after certbot and are used to stop and start Nginx.

$ sudo crontab -e
15 23 * * 0 /usr/local/sbin/certbot-auto renew --pre-hook "systemctl stop nginx" --post-hook "systemctl start nginx"

Nginx

If I were to have chosen Apache 2.4 and mod_php for my installation, Nextcloud could auto configure much of the settings required, and certbot would also have been able to use Apache to get and renew certificates. Never the less I prefer Nginx.

The first thing I did was generate a 4096-bit dhparam.pem file to use with Nginx. This file is used when computing the Diffie-Hellman key exchange.

$ sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096

I then used Mozilla’s SSL Configuration Generator to create an ssl.conf file that I would include in my Nextcloud config and modified it to reflect my requirements.

$ sudoedit /etc/nginx/snippets/ssl.conf

# From Mozilla SSL Configuration Generator
# https://mozilla.github.io/server-side-tls/ssl-config-generator/
# nginx 1.10.1 | modern profile | OpenSSL 1.0.2d
# Oldest compatible clients : Firefox 27, Chrome 30, IE 11 on Windows 7,
# 		Edge, Opera 17, Safari 9, Android 5.0, and Java 8

ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

# Diffie-Hellman parameter for DHE ciphersuites, recommmended 2048 bits
ssl_dhparam /etc/ssl/certs/dhparam.pem;

# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000;

# OCSP Stapling ---
# fetch OCSP records from URL in ssl_certificate and cache them
ssl_stapling on;
ssl_stapling_verify on;

resolver 192.168.1.1;
resolver_timeout 10s;

You’ll note many things are missing from the generator output, like anything to do with the certificates themselves. This is because I’ve chosen to modularize the configuration to allow me to reuse it for other things at a later date. Additionally, you should research HSTS prior to enabling the Strict-Transport-Security line included in that code block, if you’re unsure comment it out.

Now it was time for Nextcloud’s Nginx configuration. Much of this was taken from Nextcloud’s own documentation, however there were a few changes made to address the modularity I’d added, adjust some defaults, as well as stop 404s from happening in the root directory. I also used fastcgi_cache_path as outlined in this section of Nextcloud’s documentation, though there are some discrepancies there that I’ll address after the code block, along with the reasons for my other changes.

$ sudoedit /etc/nginx/sites-available/nextcloud

upstream php-handler {
	server unix:/run/php/php7.0-fpm.sock;
}

fastcgi_cache_path /srv/cache/nginx/ levels=1:2 keys_zone=NEXTCLOUD:100m inactive=60m;
map $request_uri $skip_cache {
	default 1;
	~*/thumbnail.php 0;
	~*/apps/galleryplus/ 0;
	~*/apps/gallery/ 0;
}

server {
	listen 443 ssl http2 default_server;
	listen [::]:443 ssl http2 default_server;

	root /srv/public/nextcloud/;
	server_name sub.example.com;
	index index.php index.html;
	access_log /var/log/nginx/nextcloud_access.log;
	error_log /var/log/nginx/nextcloud_error.log;

	# SSL Keys
	ssl_certificate /etc/letsencrypt/live/sub.example.com/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/sub.example.com/privkey.pem;
	ssl_trusted_certificate /etc/letsencrypt/live/sub.example.com/chain.pem;

	include snippets/ssl.conf;

	# Add headers to serve security related headers
	add_header X-Content-Type-Options nosniff;
	add_header X-Frame-Options "SAMEORIGIN";
	add_header X-XSS-Protection "1; mode=block";
	add_header X-Robots-Tag none;
	add_header X-Download-Options noopen;
	add_header X-Permitted-Cross-Domain-Policies none;

	location = /robots.txt {
			allow all;
			log_not_found off;
			access_log off;
	}

	# The following 2 rules are only needed for the user_webfinger app.
	# Uncomment it if you're planning to use this app.
	#rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
	#rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json
	# last;

	location = /.well-known/carddav {
			return 301 $scheme://$host/remote.php/dav;
	}
	location = /.well-known/caldav {
			return 301 $scheme://$host/remote.php/dav;
	}

	# set max upload size
	client_max_body_size 8G;
	fastcgi_buffers 64 4K;

	# Disable gzip to avoid the removal of the ETag header
	gzip off;

	# Uncomment if your server is build with the ngx_pagespeed module
	# This module is currently not supported.
	#pagespeed off;

	location = / {
			index index.php index.html;
	}

	location / {
			rewrite ^ /index.php$uri;
	}

	location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ {
			deny all;
	}
	location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) {
			deny all;
	}

	location ~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+|core/t$
			fastcgi_split_path_info ^(.+\.php)(/.*)$;
			include snippets/fastcgi-php.conf;
			fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
			fastcgi_param PATH_INFO $fastcgi_path_info;
			fastcgi_param HTTPS on;
			#Avoid sending the security headers twice
			fastcgi_param modHeadersAvailable true;
			fastcgi_param front_controller_active true;
			fastcgi_pass php-handler;
			fastcgi_intercept_errors on;
			fastcgi_request_buffering off;

			# Cache
			fastcgi_cache_bypass #skip_cache;
			fastcgi_no_cache $skip_cache;
			fastcgi_cache NEXTCLOUD;
			fastcgi_cache_valid  60m;
			fastcgi_cache_methods GET HEAD;
	}

	# Cache purging
	fastcgi_cache_key $http_cookie$request_method$host$request_uri;
	fastcgi_cache_use_stale error timeout invalid_header http_500;
	fastcgi_ignore_headers Cache-Control Expires Set-Cookie;

	location ~ ^/(?:updater|ocs-provider)(?:$|/) {
			try_files $uri/ =404;
			index index.php;
	}

	# Adding the cache control header for js and css files
	# Make sure it is BELOW the PHP block
	location ~* \.(?:css|js|woff|svg|gif)$ {
			try_files $uri /index.php$uri$is_args$args;
			add_header Cache-Control "public, max-age=7200";
			# Add headers to serve security related headers (It is intended to
			# have those duplicated to the ones above)
			# Before enabling Strict-Transport-Security headers please read into
			# this topic first.
			add_header Strict-Transport-Security "max-age=15768000";
			#  includeSubDomains; preload;";
			add_header X-Content-Type-Options nosniff;
			add_header X-Frame-Options "SAMEORIGIN";
			add_header X-XSS-Protection "1; mode=block";
			add_header X-Robots-Tag none;
			add_header X-Download-Options noopen;
			add_header X-Permitted-Cross-Domain-Policies none;
			# Optional: Don't log access to assets
			access_log off;
	}

	location ~* \.(?:png|html|ttf|ico|jpg|jpeg)$ {
			try_files $uri /index.php$uri$is_args$args;
			# Optional: Don't log access to other assets
			access_log off;
	}
}

Missing from Nextcloud’s documentation, or perhaps not required in other configurations was this block. Without this block in my config if I visited https://sub.example.com/ I would receive a 404 error. Despite this already being specified in the main server block. Also note it’s location in the code block above, it must come before the other location blocks that follow it.

location = / {
		index index.php index.html;
}

The second thing I did was change Nextcloud’s default value of 512M to 8G, allowing larger files to be uploaded to the server.

# set max upload size
client_max_body_size 8G;

The third thing was to include the ssl.conf file in the config. This would load the settings generated by Mozilla’s SSL Configuration Generator. This can be dropped into any server block, and saves you having to modify each individual site that includes it.

include snippets/ssl.conf;

I also uncommented the Strict-Transport-Security line in this section. I left the ‘includeSubDomains’ and ‘preload’ commented out however, as I was already using a subdomain, and my server isn’t in the preload list, nor will I be adding it. As mentioned previously, if you’re unsure about HSTS, leave this line commented out.

# Adding the cache control header for js and css files
# Make sure it is BELOW the PHP block
location ~* \.(?:css|js|woff|svg|gif)$ {
[..]
		# Add headers to serve security related headers (It is intended to
		# have those duplicated to the ones above)
		# Before enabling Strict-Transport-Security headers please read into
		# this topic first.
		add_header Strict-Transport-Security "max-age=15768000";
		#  includeSubDomains; preload;";
[..]
}

In Nextcloud’s documentation about using Nginx caching, they mention that you need to compile Nginx with the nginx-cache-purge module, they say this is required to use their example configuration. I’m not sure why this claim is made, as far as my understanding goes their example configuration makes use of Nginx’s nginx-http-fastcgi module, which is included by default. As such I’m using their configuration without having compiled nginx-cache-purge into my copy of Nginx, which allows me to continue using the packages that come with Ubuntu.

Finally I removed the default configuration and enabled and tested that that my configuration worked.

$ sudo rm /etc/nginx/sites-enabled/default
$ sudo ln -s /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

I then reloaded Nginx to load the new configuration.

$ sudo systemctl reload nginx

PHP-FPM

Once again because I’d chosen not to use Apache2.4 with mod_php I had to set some variables in PHP manually. I uncommented the following lines in my PHP-FPM’s pool configuration.

$ sudoedit /etc/php/7.0/fpm/pool.d/www.conf

[..]
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp
[..]

PHP then required a restart to load the new changes.

$ sudo systemctl restart php7.0-fpm

Nextcloud

After all that we finally get to Nextcloud itself. There are a number of files to download in addition to Nextcloud to check and verify it’s integrity.

$ cd /srv/public
$ sudo wget https://download.nextcloud.com/server/releases/nextcloud-11.0.2.tar.bz2
$ sudo wget https://download.nextcloud.com/server/releases/nextcloud-11.0.2.tar.bz2.sha256
$ sudo wget https://download.nextcloud.com/server/releases/nextcloud-11.0.2.tar.bz2.asc
$ sudo wget https://nextcloud.com/nextcloud.asc

The first file is Nextcloud itself, the second is it’s SHA-256 checksum, and the last two are used to verify the package.

$ sha256sum -c nextcloud-11.0.2.tar.bz2.sha256 < nextcloud-11.0.2.tar.bz2
nextcloud-11.0.2.tar.bz2: OK
$ gpg --import nextcloud.asc
$ gpg --verify nextcloud-11.0.2.tar.bz2.asc nextcloud-11.0.2.tar.bz2

Now that everything checks out, we can extract.

$ sudo tar xjf nextcloud-11.0.2.tar.bz2

The files will extract into a folder called nextcloud in the directory I switched to earlier /srv/public. The following script will ensure that all the files have the correct permissions, the 2nd to 5th line are user configurable.

$ nano ~/nextcloud_dir_config

#!/bin/bash
ocpath='/srv/public/nextcloud'
htuser='www-data'
htgroup='www-data'
rootuser='root'

printf "Creating possible missing Directories\n"
mkdir -p $ocpath/data
mkdir -p $ocpath/updater

printf "chmod Files and Directories\n"
find ${ocpath}/ -type f -print0 | xargs -0 chmod 0640
find ${ocpath}/ -type d -print0 | xargs -0 chmod 0750

printf "chown Directories\n"
chown -R ${rootuser}:${htgroup} ${ocpath}/
chown -R ${htuser}:${htgroup} ${ocpath}/apps/
chown -R ${htuser}:${htgroup} ${ocpath}/config/
chown -R ${htuser}:${htgroup} ${ocpath}/data/
chown -R ${htuser}:${htgroup} ${ocpath}/themes/
chown -R ${htuser}:${htgroup} ${ocpath}/updater/

chmod +x ${ocpath}/occ

printf "chmod/chown .htaccess\n"
if [ -f ${ocpath}/.htaccess ]
 then
  chmod 0644 ${ocpath}/.htaccess
  chown ${rootuser}:${htgroup} ${ocpath}/.htaccess
fi
if [ -f ${ocpath}/data/.htaccess ]
 then
  chmod 0644 ${ocpath}/data/.htaccess
  chown ${rootuser}:${htgroup} ${ocpath}/data/.htaccess
fi
  • ocpath is the path to your nextcloud directory
  • htuser is the user your PHP and Nginx processes run as
  • htgroup is the group your PHP and Nginx processes run as
  • rootuser is the username of your root account

Save that file and make it executable, and then run it.

$ chmod +x ~/nextcloud_dir_config
$ sudo ~/.nextcloud_dir_config

The script will now apply the file and directory permissions to your Nextcloud install. You will no longer have access to the content in /srv/public/nextcloud without using sudo or root, as only root and www-data have read access within the nextcloud directory.

I then edited the nextcloud config file to set the trusted domain, enable the opcode memcache I’d included earlier, APCu, as well as to enable the movie previous I’d installed ffmpeg for.

$ sudoedit /srv/public/nextcloud/config/config.php

[..]
  'trusted_domains' =>
  array (
    0 => 'sub.example.com',
  ),
[..]
  'memcache.local' => '\OC\Memcache\APCu',
  'enable_previews' => true,
  'enabledPreviewProviders' =>
    array (
    0 => 'OC\\Preview\\Movie',
    1 => 'OC\\Preview\\Image',
  ),
[..]

I mentioned earlier that I’d changed the Nginx config to allow 8 GB files and that a setting had to be reflected somewhere else later on. Here’s that place now, I set both the maximum execution times as well as the upload size.

$ sudoedit /srv/public/nextcloud/.user.ini

[..]
upload_max_filesize=8G
post_max_size=8G
[..]
max_execution_time 3600
max_input_time 3600
[..]

Finally, I can visit https://sub.example.com and enter the required information to connect Nextcloud up with the database created near the start. You’ll need to enter the following information:

  • New Nextcloud Administrator Username: <username>
  • New Nextcloud Administrator Password: <password>
  • Database Type: MySQL
  • MySQL Username: nextcloud
  • MySQL Password: <password>
  • MySQL Database: nextcloud

Success! Nextcloud is now installed, but I’m still not done. There are some more settings within Nextcloud that need to be set. First of those is setting up a cron job. Nextcloud by default uses an AJAX cron job, but I preferred to set one up using the systems cron service.

$ sudo crontab -u www-data -e

*/15  *  *  *  * php -f /srv/public/nextcloud/cron.php

This sets the cron job under the user www-data, which is what Nextcloud is running as via PHP-FPM, and it sets the job to run every 15 minutes. Now I confirm that the job is indeed set.

$ sudo crontab -u www-data -l
*/15  *  *  *  * php -f /srv/public/nextcloud/cron.php

Excellent.

The remainder of the settings I changed are within Nextcloud. First I set a default expiration date on shared links in the “Sharing” section of the Admin panel. While there I also ensured that public uploads was disabled. Next I enabled 2FA (2-Factor Authentication) using an “App” titled “Two Factor TOTP Provider” written by Christoph Wurst. With this “App” enabled 2FA can be enabled in each users Personal page, which I promptly did for the administrator account.

Lastly, I created a non-admin user that I would actually use as my personal user. While logged in as the administrator there’s a Users section. There you can manage users, groups, and quotas. I set my personal user a reasonably large number like 100 GB, mainly to stop me from accidentally filling up the server. If I encounter that limit I can raise it, but for now it should be well more than sufficient. I also set the administrator a low limit like 5 GB, because I never intend to use them for anything, and just in case, stop that user from filling up free space as well.

Now I could log out of the administrator account and into my newly minted personal account!

Cleanup

Now that I was done there was some clean up to do. This part is optional, but I like keeping things neat. The files downloaded to install, check, and verify Nextcloud could be removed, as well as the script that was created.

$ sudo rm /srv/public/nextcloud-*
$ sudo rm /srv/public/nextcloud.asc
$ rm ~/nextcloud_dir_config

That should clean up the mess.

Done!

If all has gone according to plan, Nextcloud should be up and running and ready to connect to the desktop or iOS/Android clients. With 2FA enabled you’ll need to use “App passwords” to connect the clients to your account, as they have no method of inputting the 2nd factor. The passwords generated by the “App” in Nextcloud are very much like serial numbers, 4 sets of 5 capital letters with hyphens between them. Unfortunately those hyphens must also be typed, this combination makes it a bit of a pain in the butt to type them on a smartphone.

Hopefully you found this useful!

-Spencer