Nextcloud 13 installation guide (Ubuntu/NGINX)


Following this guide you will be able to install and configure Nextcloud 13 based on Ubuntu 18.04 LTS, NGINX 1.15.1, openssl 1.1.0h, TLSv1.3, PHP 7.2.7, MariaDB, Redis, fail2ban, firewall (ufw) and will achieve an A+ rating from as well Nextcloud as Qualys SSL Labs. We will request and implement your ssl certificate(s) from Let’s Encrypt in chapter 5. You only have to ammend the red marked values (YOUR.DEDYN.IO, 192.168.2.x, ssh port 22) regarding your environment!


Pre-requirements

From my perspective the requirements for this guide may be rated as low: you only have to

  • provide a 64Bit Mini-Server (e.g. Intel NUC),
  • forward two ports (80 and 443) from internet (your router e.g. FritzBox or Speedport) to your internal Nextcloud server
  • and install the operating system Ubuntu 18.04 LTS (64Bit).

Table of content

  1. Prepare your server and install NGINX
  2. PHP
  3. MariaDB
  4. Redis
  5. Nextcloud (SSL enabled, A+, 100%)
  6. fail2ban and firewall (ufw)

Last Updates:

July, 10th 2018:
– added chapter 6.1 (thx to @ank0m): harden your Nextcloud using the spamhaus project

… the entire update history


1. Prepare your server and install NGINX

sudo -s
cd /usr/local/src
apt update && apt upgrade -y && apt install software-properties-common zip unzip screen curl ffmpeg libfile-fcntllock-perl -y
add-apt-repository ppa:certbot/certbot -y && apt update && apt upgrade -y && apt install letsencrypt -y
apt install language-pack-en-base -y && sudo LC_ALL=en_US.UTF-8 add-apt-repository ppa:ondrej/php -y && apt update && apt upgrade -y
apt remove nginx nginx-common nginx-full -y --allow-change-held-packages
sed -i '$adeb http://nginx.org/packages/mainline/ubuntu/ bionic nginx' /etc/apt/sources.list
sed -i '$adeb-src http://nginx.org/packages/mainline/ubuntu/ bionic nginx' /etc/apt/sources.list
wget http://nginx.org/keys/nginx_signing.key && apt-key add nginx_signing.key
apt update && apt install ssl-cert nginx -y

Modify NGINX:

systemctl enable nginx.service && apt-mark hold nginx

To update NGINX in the future just issue the following statement:

apt-mark unhold nginx && apt upgrade -y && apt-mark hold nginx

Change NGINX configuration:

mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak && vi /etc/nginx/nginx.conf

to:

user www-data;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
use epoll;
}
http {
server_names_hash_bucket_size 64;
upstream php-handler {
server unix:/run/php/php7.2-fpm.sock;
}
set_real_ip_from 127.0.0.1;
set_real_ip_from 192.168.2.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
include /etc/nginx/mime.types;
#include /etc/nginx/proxy.conf;
#include /etc/nginx/ssl.conf;
#include /etc/nginx/header.conf;
#include /etc/nginx/optimization.conf;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'"$host" sn="$server_name" '
'rt=$request_time '
'ua="$upstream_addr" us="$upstream_status" '
'ut="$upstream_response_time" ul="$upstream_response_length" '
'cs=$upstream_cache_status' ;
access_log /var/log/nginx/access.log main;
sendfile on;
send_timeout 3600;
tcp_nopush on;
tcp_nodelay on;
open_file_cache max=500 inactive=10m;
open_file_cache_errors on;
keepalive_timeout 65;
reset_timedout_connection on;
server_tokens off;
resolver 192.168.2.1;
# IPv4 and IPv6:
# resolver 192.168.2.1 [0:0:0:0:0:FFFF:C0A8:0201];
# resolver IP is your DNS e.g. your FritzBox/Router
resolver_timeout 10s;
include /etc/nginx/conf.d/*.conf;
}

Start NGINX:

service nginx restart

Create folders and apply permissions:

mkdir -p /var/nc_data /var/www/letsencrypt /usr/local/tmp/cache /usr/local/tmp/sessions /usr/local/tmp/apc /upload_tmp
chown -R www-data:www-data /upload_tmp /var/nc_data /var/www
chown -R www-data:root /usr/local/tmp/sessions /usr/local/tmp/cache /usr/local/tmp/apc

2. Install PHP

apt install php7.2-fpm php7.2-gd php7.2-mysql php7.2-curl php7.2-xml php7.2-zip php7.2-intl php7.2-mbstring php7.2-json php7.2-bz2 php7.2-ldap php-apcu imagemagick php-imagick php-smbclient -y

Awesome, PHP 7.2 is already installed.

Configure PHP:

cp /etc/php/7.2/fpm/pool.d/www.conf /etc/php/7.2/fpm/pool.d/www.conf.bak
cp /etc/php/7.2/cli/php.ini /etc/php/7.2/cli/php.ini.bak
cp /etc/php/7.2/fpm/php.ini /etc/php/7.2/fpm/php.ini.bak
cp /etc/php/7.2/fpm/php-fpm.conf /etc/php/7.2/fpm/php-fpm.conf.bak
sed -i "s/;env\[HOSTNAME\] = /env[HOSTNAME] = /" /etc/php/7.2/fpm/pool.d/www.conf
sed -i "s/;env\[TMP\] = /env[TMP] = /" /etc/php/7.2/fpm/pool.d/www.conf
sed -i "s/;env\[TMPDIR\] = /env[TMPDIR] = /" /etc/php/7.2/fpm/pool.d/www.conf
sed -i "s/;env\[TEMP\] = /env[TEMP] = /" /etc/php/7.2/fpm/pool.d/www.conf
sed -i "s/;env\[PATH\] = /env[PATH] = /" /etc/php/7.2/fpm/pool.d/www.conf
sed -i "s/pm.max_children = .*/pm.max_children = 240/" /etc/php/7.2/fpm/pool.d/www.conf
sed -i "s/pm.start_servers = .*/pm.start_servers = 20/" /etc/php/7.2/fpm/pool.d/www.conf
sed -i "s/pm.min_spare_servers = .*/pm.min_spare_servers = 10/" /etc/php/7.2/fpm/pool.d/www.conf
sed -i "s/pm.max_spare_servers = .*/pm.max_spare_servers = 20/" /etc/php/7.2/fpm/pool.d/www.conf
sed -i "s/;pm.max_requests = 500/pm.max_requests = 500/" /etc/php/7.2/fpm/pool.d/www.conf
sed -i "s/output_buffering =.*/output_buffering = 'Off'/" /etc/php/7.2/cli/php.ini
sed -i "s/max_execution_time =.*/max_execution_time = 1800/" /etc/php/7.2/cli/php.ini
sed -i "s/max_input_time =.*/max_input_time = 3600/" /etc/php/7.2/cli/php.ini
sed -i "s/post_max_size =.*/post_max_size = 10240M/" /etc/php/7.2/cli/php.ini
sed -i "s/;upload_tmp_dir =.*/upload_tmp_dir = \/upload_tmp/" /etc/php/7.2/cli/php.ini
sed -i "s/upload_max_filesize =.*/upload_max_filesize = 10240M/" /etc/php/7.2/cli/php.ini
sed -i "s/max_file_uploads =.*/max_file_uploads = 100/" /etc/php/7.2/cli/php.ini
sed -i "s/;date.timezone.*/date.timezone = Europe\/\Berlin/" /etc/php/7.2/cli/php.ini
sed -i "s/;session.cookie_secure.*/session.cookie_secure = True/" /etc/php/7.2/cli/php.ini
sed -i "s/;session.save_path =.*/session.save_path = \"N;700;\/usr\/local\/tmp\/sessions\"/" /etc/php/7.2/cli/php.ini
sed -i '$aapc.enable_cli = 1' /etc/php/7.2/cli/php.ini
sed -i "s/memory_limit = 128M/memory_limit = 512M/" /etc/php/7.2/fpm/php.ini
sed -i "s/output_buffering =.*/output_buffering = 'Off'/" /etc/php/7.2/fpm/php.ini
sed -i "s/max_execution_time =.*/max_execution_time = 1800/" /etc/php/7.2/fpm/php.ini
sed -i "s/max_input_time =.*/max_input_time = 3600/" /etc/php/7.2/fpm/php.ini
sed -i "s/post_max_size =.*/post_max_size = 10240M/" /etc/php/7.2/fpm/php.ini
sed -i "s/;upload_tmp_dir =.*/upload_tmp_dir = \/upload_tmp/" /etc/php/7.2/fpm/php.ini
sed -i "s/upload_max_filesize =.*/upload_max_filesize = 10240M/" /etc/php/7.2/fpm/php.ini
sed -i "s/max_file_uploads =.*/max_file_uploads = 100/" /etc/php/7.2/fpm/php.ini
sed -i "s/;date.timezone.*/date.timezone = Europe\/\Berlin/" /etc/php/7.2/fpm/php.ini
sed -i "s/;session.cookie_secure.*/session.cookie_secure = True/" /etc/php/7.2/fpm/php.ini
sed -i "s/;opcache.enable=.*/opcache.enable=1/" /etc/php/7.2/fpm/php.ini
sed -i "s/;opcache.enable_cli=.*/opcache.enable_cli=1/" /etc/php/7.2/fpm/php.ini
sed -i "s/;opcache.memory_consumption=.*/opcache.memory_consumption=128/" /etc/php/7.2/fpm/php.ini
sed -i "s/;opcache.interned_strings_buffer=.*/opcache.interned_strings_buffer=8/" /etc/php/7.2/fpm/php.ini
sed -i "s/;opcache.max_accelerated_files=.*/opcache.max_accelerated_files=10000/" /etc/php/7.2/fpm/php.ini
sed -i "s/;opcache.revalidate_freq=.*/opcache.revalidate_freq=1/" /etc/php/7.2/fpm/php.ini
sed -i "s/;opcache.save_comments=.*/opcache.save_comments=1/" /etc/php/7.2/fpm/php.ini
sed -i "s/;session.save_path =.*/session.save_path = \"N;700;\/usr\/local\/tmp\/sessions\"/" /etc/php/7.2/fpm/php.ini
sed -i "s/;emergency_restart_threshold =.*/emergency_restart_threshold = 10/" /etc/php/7.2/fpm/php-fpm.conf
sed -i "s/;emergency_restart_interval =.*/emergency_restart_interval = 1m/" /etc/php/7.2/fpm/php-fpm.conf
sed -i "s/;process_control_timeout =.*/process_control_timeout = 10s/" /etc/php/7.2/fpm/php-fpm.conf
sed -i '$aapc.enabled=1' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.file_update_protection=2' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.optimization=0' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.shm_size=256M' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.include_once_override=0' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.shm_segments=1' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.ttl=7200' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.user_ttl=7200' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.gc_ttl=3600' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.num_files_hint=1024' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.enable_cli=0' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.max_file_size=5M' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.cache_by_default=1' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.use_request_time=1' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.slam_defense=0' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.mmap_file_mask=/usr/local/tmp/apc.XXXXXX' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.stat_ctime=0' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.canonicalize=1' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.write_lock=1' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.report_autofilter=0' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.rfc1867=0' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.rfc1867_prefix =upload_' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.rfc1867_name=APC_UPLOAD_PROGRESS' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.rfc1867_freq=0' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.rfc1867_ttl=3600' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.lazy_classes=0' /etc/php/7.2/fpm/php.ini
sed -i '$aapc.lazy_functions=0' /etc/php/7.2/fpm/php.ini
sed -i "s/09,39.*/# &/" /etc/cron.d/php
(crontab -l ; echo "09,39 * * * * /usr/lib/php/sessionclean 2>&1") | crontab -u root -

Modify /etc/fstab

Determine the uid of your www-data user by issuing

id www-data

and only if it differs from ‘uid=33‘ replace the ‘uid=33‘ in the following rows properly before executing them!

sed -i '$atmpfs /tmp tmpfs defaults,noatime,nosuid,nodev,noexec,mode=1777 0 0' /etc/fstab
sed -i '$atmpfs /var/tmp tmpfs defaults,noatime,nosuid,nodev,noexec,mode=1777 0 0' /etc/fstab
sed -i '$atmpfs /usr/local/tmp/apc tmpfs defaults,uid=33,size=300M,noatime,nosuid,nodev,noexec,mode=1777 0 0' /etc/fstab
sed -i '$atmpfs /usr/local/tmp/cache tmpfs defaults,uid=33,size=300M,noatime,nosuid,nodev,noexec,mode=1777 0 0' /etc/fstab
sed -i '$atmpfs /usr/local/tmp/sessions tmpfs defaults,uid=33,size=300M,noatime,nosuid,nodev,noexec,mode=1777 0 0' /etc/fstab

Mount tmpfs and then restart both, PHP and NGINX:

mount -a && service php7.2-fpm restart && service nginx restart

3. MariaDB

apt update && apt install mariadb-server -y

Secure MariaDB:

mysql_secure_installation
Enter current password for root (enter for none): <ENTER>
Set root password? [Y/n] Y
Remove anonymous users? [Y/n] Y
Disallow root login remotely? [Y/n] Y
Remove test database and access to it? [Y/n] Y
Reload privilege tables now? [Y/n] Y

Configure MariaDB:

mv /etc/mysql/my.cnf /etc/mysql/my.cnf.bak && vi /etc/mysql/my.cnf

Change the entire my.cnf-file to:

[server]
skip-name-resolve
innodb_buffer_pool_size = 128M
innodb_buffer_pool_instances = 1
innodb_flush_log_at_trx_commit = 2
innodb_log_buffer_size = 32M
innodb_max_dirty_pages_pct = 90
query_cache_type = 1
query_cache_limit = 2M
query_cache_min_res_unit = 2k
query_cache_size = 64M
tmp_table_size= 64M
max_heap_table_size= 64M
slow-query-log = 1
slow-query-log-file = /var/log/mysql/slow.log
long_query_time = 1

[client-server]
!includedir /etc/mysql/conf.d/
!includedir /etc/mysql/mariadb.conf.d/

[client]
default-character-set = utf8mb4

[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_general_ci
transaction_isolation = READ-COMMITTED
binlog_format = ROW
innodb_large_prefix=on
innodb_file_format=barracuda
innodb_file_per_table=1

Restart and connect to MariaDB:

service mysql restart && mysql -uroot

Create the database and the user:

CREATE DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER nextcloud@localhost identified by 'nextcloud';
GRANT ALL PRIVILEGES on nextcloud.* to nextcloud@localhost;
FLUSH privileges;
quit;

4. Redis

apt update && apt install redis-server php-redis -y

Change configuration and group membership:

cp /etc/redis/redis.conf /etc/redis/redis.conf.bak
sed -i "s/port 6379/port 0/" /etc/redis/redis.conf
sed -i s/\#\ unixsocket/\unixsocket/g /etc/redis/redis.conf
sed -i "s/unixsocketperm 700/unixsocketperm 770/" /etc/redis/redis.conf 
sed -i "s/# maxclients 10000/maxclients 512/" /etc/redis/redis.conf
usermod -a -G redis www-data

Modify /etc/sysctl.conf and /etc/rc.local:

cp /etc/sysctl.conf /etc/sysctl.conf.bak && sed -i '$avm.overcommit_memory = 1' /etc/sysctl.conf

Reboot your server:

shutdown -r now

5. Create the nextcloud.conf:

sudo -s
mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak && touch /etc/nginx/conf.d/default.conf && vi /etc/nginx/conf.d/nextcloud.conf

Paste the following rows:

server {
server_name YOUR.DEDYN.IO;
#Your DDNS adress, (e.g. from desec.io) 
listen 80 default_server;
# IPv6:
#listen [::]:80 default_server;
location ^~ /.well-known/acme-challenge {
proxy_pass http://127.0.0.1:81;
proxy_set_header Host $host;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
server_name YOUR.DEDYN.IO;
#Your DDNS adress, (e.g. from desec.io) 
listen 443 ssl http2 default_server;
# IPv6
#listen [::]:443 ssl http2 default_server;
root /var/www/nextcloud/;
access_log /var/log/nginx/nextcloud.access.log main;
error_log /var/log/nginx/nextcloud.error.log warn;
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location = /.well-known/carddav {
return 301 $scheme://$host/remote.php/dav;
}
location = /.well-known/caldav {
return 301 $scheme://$host/remote.php/dav;
}
client_max_body_size 10240M;
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 ~ \.(?:flv|mp4|mov|m4a)$ {
mp4;
mp4_buffer_size 100m;
mp4_max_buffer_size 1024m;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
include php_optimization.conf;
fastcgi_pass php-handler;
fastcgi_param HTTPS on;
}
location ~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\.php(?:$|/) {
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
include php_optimization.conf;
fastcgi_pass php-handler;
fastcgi_param HTTPS on;
}
location ~ ^/(?:updater|ocs-provider)(?:$|/) {
try_files $uri/ =404;
index index.php;
}
location ~ \.(?:css|js|woff|svg|gif|png|html|ttf|ico|jpg|jpeg)$ {
try_files $uri /index.php$uri$is_args$args;
access_log off;
expires 360d;
}
}

If you want your Nextcloud running in a subdir like https://your.dedyn.io/nextcloud use this nextcloud.conf instead:

server {
server_name your.dedyn.io;
#Your DDNS adress, (e.g. from desec.io) 
listen 80 default_server;
# IPv6:
# listen [::]:80 default_server;
location ^~ /.well-known/acme-challenge {
proxy_pass http://127.0.0.1:81;
proxy_set_header Host $host;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
server_name your.dedyn.io;
#Your DDNS adress, (e.g. from desec.io) 
listen 443 ssl http2 default_server;
# IPv6:
# listen [::]:443 ssl http2 default_server;
root /var/www/;
access_log /var/log/nginx/nextcloud.access.log main;
error_log /var/log/nginx/nextcloud.error.log warn;
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location = /.well-known/carddav {
return 301 $scheme://$host/nextcloud/remote.php/dav;
}
location = /.well-known/caldav {
return 301 $scheme://$host/nextcloud/remote.php/dav;
}
client_max_body_size 10240M;
location ^~ /nextcloud {
location /nextcloud {
rewrite ^ /nextcloud/index.php$uri;
}
location ~ ^/nextcloud/(?:build|tests|config|lib|3rdparty|templates|data)/ {
deny all;
}
location ~ ^/nextcloud/(?:\.|autotest|occ|issue|indie|db_|console) {
deny all;
}
location ~ \.(?:flv|mp4|mov|m4a)$ {
mp4;
mp4_buffer_size 100m;
mp4_max_buffer_size 1024m;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
include php_optimization.conf;
fastcgi_pass php-handler;
fastcgi_param HTTPS on;
}
location ~ ^/nextcloud/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\.php(?:$|/) {
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
include php_optimization.conf;
fastcgi_pass php-handler;
fastcgi_param HTTPS on;
}
location ~ ^/nextcloud/(?:updater|ocs-provider)(?:$|/) {
try_files $uri/ =404;
index index.php;
}
location ~ \.(?:png|html|ttf|ico|jpg|jpeg|css|js|woff|svg|gif)$ {
try_files $uri /nextcloud/index.php$uri$is_args$args;
access_log off;
expires 360d;
}
}
}

Create the letsencrypt.conf:

vi /etc/nginx/conf.d/letsencrypt.conf

Paste the following rows:

server {
server_name 127.0.0.1;
listen 127.0.0.1:81 default_server;
charset utf-8;
access_log /var/log/nginx/le.access.log main;
error_log /var/log/nginx/le.error.log warn;
location ^~ /.well-known/acme-challenge {
default_type text/plain;
root /var/www/letsencrypt;
}
}

Create the ssl.conf:

vi /etc/nginx/ssl.conf

Paste the following rows:

ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
ssl_trusted_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
#ssl_certificate /etc/letsencrypt/live/YOUR.DEDYN.IO/fullchain.pem;
#ssl_certificate_key /etc/letsencrypt/live/YOUR.DEDYN.IO/privkey.pem;
#ssl_trusted_certificate /etc/letsencrypt/live/YOUR.DEDYN.IO/chain.pem;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384';
ssl_ecdh_curve secp521r1:secp384r1:prime256v1;
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;

Androider’s: if you run in troubles e.g. using CalDAV/CardDAV please reduce the eliptic curve and cipher strength to:

ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
ssl_ecdh_curve prime256v1;

Create the proxy.conf

vi /etc/nginx/proxy.conf

Paste the following rows:

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Server $host;
proxy_connect_timeout 3600;
proxy_send_timeout 3600;
proxy_read_timeout 3600;
proxy_redirect off;

Create the header.conf:

vi /etc/nginx/header.conf

Paste the following rows:

add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "same-origin" always;

Create the optimization.conf:

vi /etc/nginx/optimization.conf

Paste the following rows:

fastcgi_read_timeout 3600;
fastcgi_buffers 64 64K;
fastcgi_buffer_size 256k;
fastcgi_busy_buffers_size 3840K;
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;
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
gzip_disable "MSIE [1-6]\.";

Create the php_optimization.conf:

vi /etc/nginx/php_optimization.conf

Paste the following rows:

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param modHeadersAvailable true;
fastcgi_param front_controller_active true;
fastcgi_intercept_errors on;
fastcgi_request_buffering off;
fastcgi_cache_valid 404 1m;
fastcgi_cache_valid any 1h;
fastcgi_cache_methods GET HEAD;

Enhance security:

screen -S dhparam
openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096

To leave screen press STRG+A following by ‘d’ – to resume run screen -r. Please be patient, it will take a while.

Restart NGINX:

sed -i s/\#\include/\include/g /etc/nginx/nginx.conf && service nginx restart

Download and extract Nextcloud:

cd /usr/local/src
wget https://download.nextcloud.com/server/releases/latest.tar.bz2 && tar -xjf latest.tar.bz2 -C /var/www && chown -R www-data:www-data /var/www/ && rm latest.tar.bz2

Request your ssl-certificate(s):

letsencrypt certonly -a webroot --webroot-path=/var/www/letsencrypt --rsa-key-size 4096 -d YOUR.DEDYN.IO

Apply the permissions using a permissions.sh script:

vi /root/permissions.sh

Paste the following rows:

#!/bin/bash
find /var/www/ -type f -print0 | xargs -0 chmod 0640
find /var/www/ -type d -print0 | xargs -0 chmod 0750
chown -R www-data:www-data /var/www/
chown -R www-data:www-data /upload_tmp/
chown -R www-data:www-data /var/nc_data/
chmod 0644 /var/www/nextcloud/.htaccess
chmod 0644 /var/www/nextcloud/.user.ini
chmod 600 /etc/letsencrypt/live/YOUR.DEDYN.IO/fullchain.pem
chmod 600 /etc/letsencrypt/live/YOUR.DEDYN.IO/privkey.pem
chmod 600 /etc/letsencrypt/live/YOUR.DEDYN.IO/chain.pem
chmod 600 /etc/letsencrypt/live/YOUR.DEDYN.IO/cert.pem
chmod 600 /etc/ssl/certs/dhparam.pem
exit 0

Run the script:

chmod +x /root/permissions.sh && /root/permissions.sh

Modify the ssl.conf and restart NGINX:

sed -i '/ssl-cert-snakeoil/d' /etc/nginx/ssl.conf
sed -i s/\#\ssl/\ssl/g /etc/nginx/ssl.conf
service nginx restart

Create a certificate renewal automatism:

sed -i "s/SHELL=*/# &/" /etc/cron.d/certbot
sed -i "s/PATH=*/# &/" /etc/cron.d/certbot
sed -i "s/0 =*/# &/" /etc/cron.d/certbot
vi /root/renewal.sh

Paste the following rows

#!/bin/bash
cd /etc/letsencrypt
letsencrypt renew
result=$(find /etc/letsencrypt/live/ -type l -mtime -1 )
if [ -n "$result" ]; then
/usr/sbin/service nginx stop
/usr/sbin/service mysql restart
/usr/sbin/service redis-server restart
/usr/sbin/service php7.2-fpm restart
/usr/sbin/service nginx restart
fi
exit 0

Make it exectuable and create a cronjob

chmod +x /root/renewal.sh && crontab -e

Paste the red row below existing ones:

09,39 * * * * /usr/lib/php/sessionclean 2>&1
@monthly /root/renewal.sh 2>&1

Install Nextcloud silently

sudo -u www-data php /var/www/nextcloud/occ maintenance:install --database "mysql" --database-name "nextcloud" --database-user "nextcloud" --database-pass "nextcloud" --admin-user "YourNextcloudAdmin" --admin-pass "YourNextcloudAdminPasssword" --data-dir "/var/nc_data"

Information:

–database-user “nextcloud” : As set above while creating the database and user

–database-pass “nextcloud” : As set above while creating the database and user

–admin-user “YourNextcloudAdmin” : your free choice

–admin-pass “YourNextcloudAdminPasssword” : your free choice

Finish the installation, then make ammendments to your config.php as www-data:

sudo -u www-data php /var/www/nextcloud/occ config:system:set trusted_domains 1 --value=your.dedyn.io
sudo -u www-data php /var/www/nextcloud/occ config:system:set overwrite.cli.url --value=https://your.dedyn.io
sudo -u www-data cp /var/www/nextcloud/config/config.php /var/www/nextcloud/config/config.php.bak

Expand your Nextcloud config.php:

sudo -u www-data sed -i 's/^[ ]*//' /var/www/nextcloud/config/config.php
sudo -u www-data sed -i '/);/d' /var/www/nextcloud/config/config.php
sudo -u www-data cat <<EOF >>/var/www/nextcloud/config/config.php
'activity_expire_days' => 14,
'auth.bruteforce.protection.enabled' => true,
'blacklisted_files' => 
array (
0 => '.htaccess',
1 => 'Thumbs.db',
2 => 'thumbs.db',
),
'cron_log' => true,
'enable_previews' => true,
'enabledPreviewProviders' => 
array (
0 => 'OC\\Preview\\PNG',
1 => 'OC\\Preview\\JPEG',
2 => 'OC\\Preview\\GIF',
3 => 'OC\\Preview\\BMP',
4 => 'OC\\Preview\\XBitmap',
5 => 'OC\\Preview\\Movie',
6 => 'OC\\Preview\\PDF',
7 => 'OC\\Preview\\MP3',
8 => 'OC\\Preview\\TXT',
9 => 'OC\\Preview\\MarkDown',
),
'filesystem_check_changes' => 0,
'filelocking.enabled' => 'true',
'htaccess.RewriteBase' => '/',
'integrity.check.disabled' => false,
'knowledgebaseenabled' => false,
'logfile' => '/var/nc_data/nextcloud.log',
'loglevel' => 2,
'logtimezone' => 'Europe/Berlin',
'log_rotate_size' => 104857600,
'maintenance' => false,
'memcache.local' => '\\OC\\Memcache\\APCu',
'memcache.locking' => '\\OC\\Memcache\\Redis',
'overwriteprotocol' => 'https',
'preview_max_x' => 1024,
'preview_max_y' => 768,
'preview_max_scale_factor' => 1,
'redis' => 
array (
'host' => '/var/run/redis/redis-server.sock',
'port' => 0,
'timeout' => 0.0,
),
'quota_include_external_storage' => false,
'share_folder' => '/Shares',
'skeletondirectory' => '',
'theme' => '',
'trashbin_retention_obligation' => 'auto, 7',
'updater.release.channel' => 'stable',
);
EOF

Edit the .user.ini:

sudo -u www-data sed -i "s/upload_max_filesize=.*/upload_max_filesize=10240M/" /var/www/nextcloud/.user.ini
sudo -u www-data sed -i "s/post_max_size=.*/post_max_size=10240M/" /var/www/nextcloud/.user.ini
sudo -u www-data sed -i "s/output_buffering=.*/output_buffering='Off'/" /var/www/nextcloud/.user.ini
service php7.2-fpm restart && service nginx restart

Adjust Nextcloud Apps

sudo -u www-data php /var/www/nextcloud/occ app:disable survey_client
sudo -u www-data php /var/www/nextcloud/occ app:disable firstrunwizard
sudo -u www-data php /var/www/nextcloud/occ app:enable admin_audit
sudo -u www-data php /var/www/nextcloud/occ app:enable files_pdfviewer

Logon to your brandly new Nextcloud in your browser

https://your.dedyn.io/login

If the integrity check within Nextcloud will fail, try to change the config.php

sudo -u www-data vi /var/www/nextcloud/config/config.php

and set :

'integrity.check.disabled' => true,

Then restart all services:

service php7.2-fpm restart && service redis-server restart && service nginx restart

Re-run the integrity check and set the value back to ‘false’:

sudo -u www-data vi /var/www/nextcloud/config/config.php
'integrity.check.disabled' => false,

Restart all services again.

service php7.2-fpm restart && service redis-server restart && service nginx restart

and the message should disappear!


Optimize your Nextcloud using a script

vi /root/optimize.sh
#!/bin/bash
redis-cli -s /var/run/redis/redis-server.sock <<EOF
FLUSHALL
quit
EOF
sudo -u www-data php /var/www/nextcloud/occ files:scan --all
sudo -u www-data php /var/www/nextcloud/occ files:scan-app-data
exit 0
chmod +x /root/optimize.sh

Issue the optimize.sh script once:

/root/optimize.sh

Add Nextcloud cronjobs for www-data and root

For www-data:

crontab -u www-data -e

Paste the following rows

*/15 * * * * php -f /var/www/nextcloud/cron.php > /dev/null 2>&1

For root:

crontab -e

Paste the follwoing rows:

5 1 * * * /root/optimize.sh > /dev/null 2>&1

Don’t forget to switch from Ajax to Cron in Nextclouds-Adminpanel or use Nextcloud CLI to switch immediately:

sudo -u www-data php /var/www/nextcloud/occ background:cron

Finally verify your server security level

(1)

https://www.ssllabs.com/ssltest/analyze.html?d=your.dedyn.io

(2)

https://scan.nextcloud.com

(3)

https://observatory.mozilla.org/analyze/your.dedyn.io


6. Harden your System using fail2ban and ufw

First install and configure fail2ban and finally configure the firewall ufw to secure and harden Nextcloud.

Install and configure fail2ban:

sudo -s
apt update && apt install fail2ban -y

Create the Nextcloud-filter:

vi /etc/fail2ban/filter.d/nextcloud.conf

Paste the following lines:

[Definition]
failregex=^{"reqId":".*","remoteAddr":".*","app":"core","message":"Login failed: '.*' \(Remote IP: ''\)","level":2,"time":".*"}$
 ^{"reqId":".*","level":2,"time":".*","remoteAddr":".*","app":"core".*","message":"Login failed: '.*' \(Remote IP: ''\)".*}$
 ^.*\"remoteAddr\":\"\".*Trusted domain error.*$

Create a new jail:

vi /etc/fail2ban/jail.d/nextcloud.local

Paste the following rows:

[nextcloud]
backend = auto
enabled = true
port = 80,443
protocol = tcp
filter = nextcloud
maxretry = 3
bantime = 36000
findtime = 36000
logpath = /var/nc_data/nextcloud.log

[nginx-http-auth]
enabled = true

Re-start the fail2ban-service:

service fail2ban restart

Configure your ufw (uncomplicated firewall):

apt install ufw -y && ufw allow 80/tcp && ufw allow 443/tcp && ufw allow 22/tcp

Enable and restart ufw by running

ufw logging medium && ufw default deny incoming && ufw enable && service ufw restart

6.1 Harden your Nextcloud using Spamhaus Project and UFW

If you’d like to prevent “unprivileged visitors” just create the script /root/ufw-spamhaus.sh and block them by ufw directly.

vi /root/ufw-spamhaus.sh

Paste the following rows:

#!/bin/bash
# Thanks to @ank0m
EXEC_DATE=`date +%Y-%m-%d`
SPAMHAUS_DROP='/usr/local/src/drop.txt'
SPAMHAUS_eDROP='/usr/local/src/edrop.txt'
URL='https://www.spamhaus.org/drop/drop.txt'
eURL='https://www.spamhaus.org/drop/edrop.txt'
DROP_ADD_TO_UFW='/usr/local/src/DROP2.txt'
eDROP_ADD_TO_UFW='/usr/local/src/eDROP2.txt'
DROP_ARCHIVE_FILE='/usr/local/src/DROP_$EXEC_DATE'
eDROP_ARCHIVE_FILE='/usr/local/src/eDROP_$EXEC_DATE'
# All credits for the following BLACKLISTS goes to "The Spamhaus Project" - https://www.spamhaus.org
echo "Start time: $(date)"
echo " "
echo "Download daily DROP file:"
wget -q -O - "$URL" > $SPAMHAUS_DROP
grep -v '^;' $SPAMHAUS_DROP | cut -d ' ' -f 1 > $DROP_ADD_TO_UFW
echo " "
echo "Extract DROP IP addresses and add to UFW:"
cat $DROP_ADD_TO_UFW | while read line
do
ufw insert 1 deny from "$line" comment 'DROP_Blacklisted_IPs'
done
echo " "
echo "Downloading eDROP list and import to UFW"
echo " "
echo "Download daily eDROP file:"
wget -q -O - "$eURL" > $SPAMHAUS_eDROP
grep -v '^;' $SPAMHAUS_eDROP | cut -d ' ' -f 1 > $eDROP_ADD_TO_UFW
echo " "
echo "Extract eDROP IP addresses and add to UFW:"
cat $eDROP_ADD_TO_UFW | while read line
do
ufw insert 1 deny from "$line" comment 'eDROP_Blacklisted_IPs'
done
echo " "
#####
## To remove or revert these rules, keep the list of IPs!
## Run a command like so to remove the rules:
# while read line; do ufw delete deny from $line; done < $ARCHIVE_FILE
#####
echo "Backup DROP IP address list:"
mv $DROP_ADD_TO_UFW $DROP_ARCHIVE_FILE
echo " "
echo "Backup eDROP IP address list:"
mv $eDROP_ADD_TO_UFW $eDROP_ARCHIVE_FILE
echo " "
echo End time: $(date)

Make the script exutable by issuing

chmod +x /root/ufw-spamhaus.sh

and configure it in your crontab to be issued automatically.

(crontab -l ; echo "10 2 * * * /root/ufw-spamhaus.sh 2>&1") | crontab -u root -

Finally perform an initial run

/root/ufw-spamhaus.sh

and many UFW rules will be applied immediately. Be patient, it may take a while.


Enjoy your personal data in your secured and hardened Nextcloud-Server!

Don’t forget to Backup your Nextcloud

Find more instructions here: Nextcloud Backup and Restore



Carsten Rieger

131 Responses

  1. johnz says:

    and from the error log i have this error
    2018/07/13 01:24:57 [error] 1724#1724: *185 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 192.168.113.2, server: cloud.mifo.gr, request: “GET /apps/w2g2/lock?files=[[“9″,”Documents”,null,””,null,”dir”],[“3″,”Photos”,null,””,null,”dir”],[“530″,”SMB”,null,””,null,”dir”],[“8″,”Nextcloud.mp4″,null,””,null,”file”],[“7″,”Nextcloud+Manual.pdf”,null,””,null,”file”],[“157897″,”test.odt”,null,””,null,”file”],[“156528″,”testdoc”,null,””,null,”file”]]&folder=/ HTTP/1.1″, upstream: “fastcgi://unix:/run/php/php7.2-fpm.sock:”, host: “cloud.mifo.gr”
    2018/07/13 04:59:05 [error] 1724#1724: *849 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 192.168.113.2, server: cloud.mifo.gr, request: “GET /apps/w2g2/lock?files=[[“9″,”Documents”,null,””,null,”dir”],[“3″,”Photos”,null,””,null,”dir”],[“530″,”SMB”,null,””,null,”dir”],[“8″,”Nextcloud.mp4″,null,””,null,”file”],[“7″,”Nextcloud+Manual.pdf”,null,””,null,”file”],[“157897″,”test.odt”,null,””,null,”file”],[“156528″,”testdoc”,null,””,null,”file”]]&folder=/ HTTP/1.1″, upstream: “fastcgi://unix:/run/php/php7.2-fpm.sock:”, host: “cloud.mifo.gr”
    2018/07/13 05:01:49 [error] 1724#1724: *981 FastCGI sent in stderr: “Primary script unknown” while reading response header from upstream, client: 94.66.222.74, server: cloud.mifo.gr, request: “GET /nextcloud/index.php/apps/files/api/v1/thumbnail/256/256/Nextcloud.mp4 HTTP/1.1”, upstream: “fastcgi://unix:/run/php/php7.2-fpm.sock:”, host: “cloud.mifo.gr”
    2018/07/13 05:02:19 [error] 1724#1724: *1007 FastCGI sent in stderr: “Primary script unknown” while reading response header from upstream, client: 94.66.222.74, server: cloud.mifo.gr, request: “GET /nextcloud/index.php/apps/files/api/v1/thumbnail/256/256/Nextcloud.mp4 HTTP/1.1”, upstream: “fastcgi://unix:/run/php/php7.2-fpm.sock:”, host: “cloud.mifo.gr”
    2018/07/13 05:03:23 [error] 1724#1724: *1007 FastCGI sent in stderr: “Primary script unknown” while reading response header from upstream, client: 94.66.222.74, server: cloud.mifo.gr, request: “GET /nextcloud/index.php/apps/files/api/v1/thumbnail/256/256/Nextcloud.mp4 HTTP/1.1”, upstream: “fastcgi://unix:/run/php/php7.2-fpm.sock:”, host: “cloud.mifo.gr”
    2018/07/13 05:04:09 [error] 1724#1724: *1006 FastCGI sent in stderr: “Primary script unknown” while reading response header from upstream, client: 94.66.222.74, server: cloud.mifo.gr, request: “GET /nextcloud/index.php/apps/files/api/v1/thumbnail/256/256/Nextcloud.mp4 HTTP/1.1”, upstream: “fastcgi://unix:/run/php/php7.2-fpm.sock:”, host: “cloud.mifo.gr”

    do you have any idea for that ?
    i think the problem is with web server confing

  2. johnz says:

    hello
    thanks for your script , untill now i use only apache ,but with your script i try the nginx ,but
    i have a serius problem with smbclient , Module ‘smbclient’ already loaded at Unknown#0
    And with androip mobile app i cann’t explore my smb share (smb share is windows 2012R2)
    nextcloud 13.04 ,with ubundu 18.04 ,full update, intrgration with AD
    have you any suggestion for that issue ?

  3. Patrick Chou says:

    Hi! Do you know why fail2ban is always blocking my WAN IP when someone is trying to bruteforce the Nextcloud site? Already tried to bruteforce it by myself when I was connected via 4G with IP address that I know but the blocked ip was not the 4G, but the ip that is provided from the ISP – where is connected the router and the Odroid C2 server. After the jail is activated nobody can connect to the cloud, no mather what is their ip address.
    Thanks!

    • Strange behaviour! Were your IPs forwared properly (xff) or were the local IPs written to the logs? How did you configure fail2ban and what was written to your nextcloud.log?

  4. Marcus says:

    Hallo,

    ist es möglich es so einzurichten, dass dies ohne direkte Weiterleitung zur DynDNS geh? Sprich wenn ich meine IP 192.168.178.X des Server lokal eingebe?

    • Guten Morgen Marcus, wenn ich Dich richtig verstehe lautet die Antwort ja. Die NGINX Konfiguration (nextcloud.conf) sollte zusätzlich zu der Domain auch die IP enthalten (server_name YOUR.DEDYN.IO 192.168.178.X;) und in der /var/www/nextcloud/config/config.php die IP-Adresse als trusted-domain ergänzt (sudo -u www-data php /var/www/nextcloud/occ config:system:set trusted_domains 1 –value=192.168.178.X) und der overwrite.cli.url Eintrag entfernt werden. Meintest Du das?

  5. Robin says:

    Hi,
    Regarding this update:
    July, 8th 2018:
    – MariaDB changes: transaction_isolation = READ-COMMITTED, binlog_format = ROW

    Can I that already have a Nextcloud server running change this?
    Or do I break something then?

    • Hi Robin, yes you can! Please follow these steps:
      sudo -s
      sudo -u www-data /var/www/nextcloud/occ maintenance:mode --on
      service nginx stop && service mysql stop
      cp /etc/mysql.my.cnf /etc/mysql.my.cnf.bak
      vi /etc/mysql.my.cnf

      Make your changes
      transaction_isolation = READ-COMMITTED
      binlog_format = ROW

      Restart your services
      service mysql restart && service nginx restart

      Disable Nextclouds maintenance mode:
      sudo -u www-data /var/www/nextcloud/occ maintenance:mode --off

  6. Love your walk-through! I got it working a month or so back but ran into a functional snag at the end. I decided to start a small blog to document these experiences to serve as my own documentation and to help others (hopefully). I linked this guide at my site as I hope you don’t mind.

    My question being a little new to wordpress, what syntax plugin do you use for your code-blocks?

  7. Rainer says:

    Hallo Carsten,

    ich hatte Nextcloud im Mai installiert. Läuft soweit auch alles. Aber jetzt erst habe ich den Client getestet und erhalte die folgende Fehlermeldung, wenn ich eine Datei/Ordner über den Client an eine externe E-Mail Adresse verschicken möchte:

    “Sharing test.jpg failed, could not find meine@emailadresse.de, maybe the server is currently unreachable or uses a self-signed certificate.”

    Wenn ich das über die die Weboberfläche mache, klappts. Hier es allerdings so, dass mir die externe Mailadresse zweimal angezeigt wird, einmal in Klammern mit “entfernt”, wenn ich das anklicke, erscheint die gleiche Fehlermeldung,
    und einmal in Klammern mit “E-Mail”, wenn ich das nehme, klappts. Scheint also so zu sein, dass der Client mir nur die erste Option anbietet u. deshalb die gleiche Fehlermeldung erscheint. Hast Du eine Idee, woran das liegen könnte?

    VG
    Rainer

  8. Francis says:

    Hi Carsten,

    thanks for your tutorial!

    Since changes from latest updates in the guide, I cannot start the nginx service anymore. Getting this error :

    nginx.service – nginx – high performance web server
    Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
    Active: failed (Result: exit-code) since lun 2018-07-02 17:57:28 EDT; 3s ago
    Docs: http://nginx.org/en/docs/
    Process: 25555 ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf (code=exited, status=1/FAILURE)

    jui 02 17:57:28 server systemd[1]: Starting nginx – high performance web server…
    jui 02 17:57:28 server nginx[25555]: nginx: [emerg] no “ssl_certificate” is defined for the “listen … ssl” directive in /etc/nginx/conf.d/nextcloud.conf:13
    jui 02 17:57:28 server systemd[1]: nginx.service: Control process exited, code=exited status=1
    jui 02 17:57:28 server systemd[1]: Failed to start nginx – high performance web server.
    jui 02 17:57:28 server systemd[1]: nginx.service: Unit entered failed state.
    jui 02 17:57:28 server systemd[1]: nginx.service: Failed with result ‘exit-code’.

    I tried to check against your version and all my files where OK. I tried to generate new certificates too and the error persists.

    Could you help me please?

    • Please verify your path to your certificates in your ssl.conf.
      A suffix (_1) might have been added while you did a new request. Ensure your ssl.conf is not commented in your nginx.conf as well. If you will still run in trouble please zip your conf files and drop them here.

      • Francis says:

        Hi,

        I just upload the config files but just after, I realized that ssl.conf, proxy.conf, header.conf and optimization.conf were commented in nginx.conf. I uncommented them and the service started fine.

        However, when I’m trying to access nextcloud now, I get an “DNS_PROBE_FINISHED_NXDOMAIN” error. What could it be?

        Regards

        • Francis says:

          I get this error “DNS_PROBE_FINISHED_NXDOMAIN” only inside my network. Via external network I can access it.

          Thank you.

          Francis

  9. Daniel says:

    Hi Carsten, vielen Dank für das mega Tutorial, es funktioniert auch alles wie gewünscht. Ich habe nur ein etwas seltsames verhalten. Beim Upload von Dateien (>100MB) fällt mir auf, dass ich je Browser-Session einen max. Upload von 7-8Mbit erhalte (auch bei 2, oder 3 gleichzeitigen Uploads, über mehrere Browser-Fenster verteilt). Der Server ist mit 100Mbit (Up/Down) am Netz. Über die Nextcloud-App “Flowupload” oder Webdav bekomme ich deutlich höhere Raten. Hast du eine Idee dazu, wo hier das Bottle-Neck sein könnte? Vielen Dank im Voraus…

    • Ich sehe keinen Zusammenhang mit der Nextcloud. Hier spielt eher Netzwerktechnik eine Rolle. Ist der Server im LAN o. im WAN mit 100/100 erreichbar? Ich denke, Dir steht zwar diese Bandbreite technisch zur Verfügung, diese wird aber bei der Kommunikation aufgeteilt und wieder zusammengesetzt. Bitte wende Dich an Deinen Provider o. an die Community.

  10. Marco says:

    Hey, i was wondering if it’s possible to use multiple hdd for data storage, does it require some extra config?
    Also is it a good idea / makes it faster to install ubuntu on an SD drive but the cloud data on the hdd/s ?

    • This can be done by RAID, by Nextclouds External Storage App, by mount NAS, etc…from my perspective no generic solution exists.
      Yes, it would make sense to operate on a SSD and e.g. on “normal” HDD or NAS devices for your cloud data. Cheers, Carsten

      • Marco says:

        I was able to put online a test version (with a test hdd) of my server with your guide, thank you for that!
        Another question i have now, for what is the backup needed? Does it saves the files or only the configs? And if so, do you have to put it on a safe drive in case your hdd dies?
        I plan to do a RAID 1 configuraton (so a full copy of files) do i still need to do a backup?

        Really much appreciate your work, thank you for everything!

  11. Robin says:

    I’m wondering what changes you did with the nginx.conf file during this update:
    June, 13th 2018:
    – updated the nginx.conf and added a “server security verification”

  12. Bruno says:

    Hello Carsten,

    Thanks a lot for the tutorial!
    Can you help me understand why you’re using snakeoil certificates instead of the letsencrypt ones in ssl.conf nginx file?
    ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
    ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
    ssl_trusted_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
    #ssl_certificate /etc/letsencrypt/live/YOUR.DEDYN.IO/fullchain.pem;
    #ssl_certificate_key /etc/letsencrypt/live/YOUR.DEDYN.IO/privkey.pem;
    #ssl_trusted_certificate /etc/letsencrypt/live/YOUR.DEDYN.IO/chain.pem;

    Thanks and cheers,
    Bruno

    • these scripts were used temporarily until you request your certificates. In a following step the rows will be deleted and the others uncommented by issuing
      sed -i '/ssl-cert-snakeoil/d' /etc/nginx/ssl.conf
      sed -i s/\#\ssl/\ssl/g /etc/nginx/ssl.conf

  13. Nick says:

    Carsten, thanks for this EXCELLENT article. I’ve managed to create another NC instance by following your instruction.

    However, when I attempted to add on this new instance with another EXCELLENT article of yours (adding clamav, found here: https://www.c-rieger.de/nextcloud-more-secure-using-clamav/). I get all the way down to “Change to the admin panel and select Additional Settings”, I don’t see “Antivirus Configuration” section.

    Any thoughts/suggestions is greatly appreciated!

  14. Bart says:

    Thank you very much for this tutorial!!

    I am a bit of a noob but trying to understand instead of just copy pasting 🙂 I have a question about “Harden your System using fail2ban….” I suppose it doesn’t matter whether you use Nginx or apache for these settings. Could you maybe shine some light on the rules? What do they actually mean. Seem quite complicated to me 🙂

    Kind regards

    Bart

    • Hi Bart,

      ^{"reqId":".*","remoteAddr":".*","app":"core","message":"Login failed: '.*' \(Remote IP: 'HOST'\)","level":2,"time":".*"}$
      ^{"reqId":".*","level":2,"time":".*","remoteAddr":".*","app":"core".*","message":"Login failed: '.*' \(Remote IP: 'HOST'\)".*}$

      The Nextcloud logfile will be parsed using this regular expressions concerning “LOGIN FAILED”
      ^.*\"remoteAddr\":\"HOST\".*Trusted domain error.*$
      The Nextcloud logfile will be parsed using this regular expressions concerning “TRUSTED DOMAIN ERRORS”

      HOST is the placeholder for the remote-IP – this IP would be blocked if fail2ban rules grab.
      Regards, Carsten

  15. Sven says:

    Hi, first of all: Thank you!!! 🙂
    Now to my question: i followed your guide step by step but now im at the step where i want to request my ssl certificates from letsencrypt.
    i tried but i got the following error: The server could not connect to the client to verify the domain :: Fetching http:///.well-known/acme-challenge/
    First i thought i forgot to adjust my firewall settings, but they are fine. Then i checked if nginx was running and i found out that nginx couldn’t be started.
    I tried “service nginx restart” but i get the following error:

    root@nextcloud:/etc/nginx/conf.d# service nginx restart
    Job for nginx.service failed because the control process exited with error code.
    See “systemctl status nginx.service” and “journalctl -xe” for details.

    When i use “systemctl status nginx.service” i get:
    ● nginx.service – nginx – high performance web server
    Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
    Active: failed (Result: exit-code) since Sat 2018-06-16 08:39:30 UTC; 54s ago
    Docs: http://nginx.org/en/docs/
    Process: 2342 ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf (code=exited, status=1/FAILURE)

    Jun 16 08:39:30 nextcloud systemd[1]: Starting nginx – high performance web server…
    Jun 16 08:39:30 nextcloud nginx[2342]: nginx: [emerg] no “ssl_certificate” is defined for the “listen … ssl” directive in /etc/nginx/conf.d/nextcloud.conf:15
    Jun 16 08:39:30 nextcloud systemd[1]: nginx.service: Control process exited, code=exited status=1
    Jun 16 08:39:30 nextcloud systemd[1]: nginx.service: Failed with result ‘exit-code’.
    Jun 16 08:39:30 nextcloud systemd[1]: Failed to start nginx – high performance web server.

    In /etc/nginx/conf.d/nextcloud.conf:15 the server block for the ssl vhost starts.
    I copied yours but it seems nginx is missing the listen … ssl directories.

    Can you please help me out here?

  16. OverMark says:

    Hi!
    Amazing article! Congratulations! By far the most detailed help that I’ve found.

    I’ve followed it step by step but I’ve changed:
    – Letsecrypt cert with one of mine.
    – Data is in a secondary disk.

    After installing NextCloud, I’m facing a login infinite loop: https://mydomain.com/login –> https://mydomain.com/login?redirect_url=/apps/files/

    But it’s the same login page and it stays here forever. No matter how many times I tried to login.

    Can you help me please?

    Thanks!

  17. Edgard says:

    Hi,

    I have Deluge without SSL and using port 8112 running on the same server. But when I try to connect to the address using the DNS name http://web.server.com:8112, I’m automatically forwarded to http insteads. However, I don’t have that problem if I use the IP addres instead so http://192.168.1.10:8112 is working.

    So my question is, is it possible to disable the http to https auto forwarding for other ports than port 80?

  18. Robin says:

    When I changing to:
    deb http://nginx.org/packages/mainline/ubuntu/ bionic nginx
    deb-src http://nginx.org/packages/mainline/ubuntu/ bionic nginx

    from:
    deb http://nginx.org/packages/mainline/ubuntu/ xenial nginx
    deb-src http://nginx.org/packages/mainline/ubuntu/ xenial nginx

    I get this error:
    N: Skipping acquire of configured file ‘nginx/binary-i386/Packages’ as repository ‘http://nginx.org/packages/mainline/ubuntu bionic InRelease’ doesn’t support architecture ‘i386’

    How can I fix it?

    Thanks.

  19. Bartos says:

    Hallo Carsten,

    zu erst vielen Dank! Ich habe selten solch eine professionelle Anleitung gesehen. Ich habe nur noch ein frage zu fail2ban. Ich betreibe die Cloud hinter einer Sophos Firewall und setze die Web Application Firewall der Sophos ein(Reverse Proxy). Diese unterstützt das weitergeben der realen IP der Clients über den X-Forwarded-For-Header. Was muss ich machen damit der fail2ban Jail diese IP für seine Auswertung nutzt? Muss ich überhaupt was umstellen?

    Mit freundlichen Grüßen

    Bartos

  20. Viktorio says:

    Hi Carsten!

    Thanks for this wonderful step by step tutorial. It just works flawless although I am not a Linux guru . Some day if you have the time of course, can you show us how to setup “Nextcloud Talk”? I’ve already tried https://help.nextcloud.com/t/howto-setup-nextcloud-talk-with-turn-server/30794 but unfortunately with no result.

    Thanks again!

  21. Larry says:

    This is by far the most excellent article on how to install NextCloud on Ubuntu. I used your article for 16.04, and just used it for 18.04. I have never seen anything that is not only so complete, but actually works very well. Thank you again. By the way, I was able to install using this guide on an ARM server with no issues. Thank you!

  22. Edgard says:

    Well, it’s working now 🙂

    The certificate was OK, but I still remade my config.php file and now everything work. I probably made a mistake in my copy/paste somewhere at that step… :-/

    Really sorry for disturbing you for nothing and thanks a lot for your help!!! (And for your article :-)))))))

  23. Edgard says:

    Thanks a lot for your article, but I do have a problem when at the “Logon to your brandly new Nextcloud in your browser” step. I did everything exactly like in the article on a newly installed Ubuntu 18.04 and didn’t received any error during the process until there.

    Firefox is giving me the error : Secure Connection Failed. The connection was interrupted while the page was loading. The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.
    And IE is giving this one : Can’t connect securely to this page. This might be because the site uses outdated or unsafe TLS security settings. If this keeps happening, try contacting the website’s owner.

    A CURL is giivng me the following result : curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL

    Would you have an idea at what I did wrong?

  24. Semko says:

    Hello Mr. Rieger.

    Because I’m planing to build my own energy efficient server, I have already read a few tutorials on the Internet, and I can say – This website is completely different beer! On this pages I found almost everything that I need. You seem very familiar with this matter. That’s why I’d like to ask You a few question.

    Can You please suggest me what kind of hardware is good choice for a project including:
    – OS – Ubuntu – 18.04;
    – Web Server – Nginx/Php/MariaDB/Wordpress;
    – Cloud server – Nextcloud;
    – Mail server – Postfix, Dovecot, SpamAssassin;
    – XMPP server – Prosody;

    The main goal is: Small, quiet, energy efficient system/board for 5 to max. 10 users.

    I know that ARM based SBC can work 24/7 for years with no problems at all , and most important for me – without active cooling. Just hate noisy things in my house. But I’m not sure if an ARM based SBC can handle all that.
    I’m more than sure that an Intel NUC with i5 processor is decades ahead, but I have no idea if it is OK with this 24/7 working cycle. And also not sure how noisy it is going to be.

    Every suggestion is highly appreciated.

    Thanks for Your time!

    • Dear Semko, thank you very much. It depends on the amount of data and where to store all your data. It would be absolutely sufficient to use an odroid c2 or rock64 (0/zero db) or an intel nuc as i do (NUC6CAYH, noisless if you reduce the rpm in bios / 24/7 since 1,5 years.) My data are stored on a Synology DS and are mounted to Nextcloud (/etc/fstab ~ 2TB). It is already discussed in the Nextcloud Forum / Best cheap hardware. But for sure: both, the odroid C2 as well the NUC would be sufficient regarding your described purposes. Cheers, Carsten

  25. Robin says:

    Hi,
    Sorry for all of my questions but in the nextcloud.conf file it’s a line proxy_pass http://127.0.0.1:81; and if I’m using the port 80 should I adjust that to 80 or should I still have it on 81?
    The port’s that are open @ my server is port 80 and 443

  26. Pisoko says:

    Hey Carsten, wie immer super HowTo!
    Mir ist bei meiner Installation (~200user) aufgefallen, dass das /usr/local/tmp/sessions sehr schnell voll läuft und die user sich anschließend nicht mehr anmelden können.
    Du solltest vielleicht ein cronjob schreiben, der diese tmp folders alle paar tage frei räumt.

    Viele Grüße

    • Vielen Dank! Es gibt ja einen cronjob, der das sogar stündlich zwei Mal tut. Dieser wird bei den PHP-Modifikationen mit angelegt (crontab -l ; echo "09,39 * * * * /usr/lib/php/sessionclean 2>&1") | crontab -u root -

  27. Bernd says:

    Nachdem ich Deine Anleitung durchgearbeitet habe kriege ich noch folgende Meldung in den Grundeinstellungen angezeigt:

    Dieser Server hat keine funktionierende Internetverbindung: Mehrere Ziele konnten nicht erreicht werden. Dies bedeutet, dass einige Funktionen, wie das Einhängen exernen Speichers, Benachrichtigungen über Updates oder die Installation von Drittanbieter-Apps nicht funktionieren. Der Zugriff auf entfernte Dateien und das Senden von E-Mail-Benachrichtigungen wird wahrscheinlich ebenfalls nicht funktionieren. Um alle Funktionen nutzen zu können, stelle eine Internet-Verbindung für diesen Server her.

    Google.de oder Nextcloud.com kann ich erfolgreich anpingen

    • Hast Du in der nginx.conf die Resolver-IP und in der nextcloud.conf den Servernamen angepasst? Was steht in den Logfiles?

      • Bernd says:

        Ich bin am Wochenende etwas weiter gekommen:
        Ich habe die Zeile “requirepass ” in der redis.conf (in Deinem Tutorial eh nur optional) mal auskommentiert und siehe da: nun kriege ich die Fehlermeldung nicht mehr und Nextcloud rennt, wie es sollt.
        Verstanden habe ich den redis-Teil nicht…kann im Moment auch gut damit leben; würde nur erstmal die Zusammenhänge verstehen wollen.

  28. @Carsten: Would it be interresting to include the GeoIP Module in the nginx, to be able to further block request by locations ?

  29. Is it possible to get an automatic mail, when updates appear to your very nice script?
    Thanks in advance

  30. Robin says:

    Hi,
    I can see that you have updated some things in the guide, and I’m wondering if we that did install it before your updates should also change it?

    Create and add optimize.sh to root crontab

    And also do the changes to the config.php

    • Robin says:

      I can’t edit the post above but I was wondering what you have changed in the config.php file?

      • I just updated the process of making ammendments to the config.php not the content itself.

        • Robin says:

          OK I see,
          When I’m trying to run the optimize.sh script I get this:
          root@nextcloud:~# /root/optimize.sh
          (error) NOAUTH Authentication required.

          Scanning AppData for files

          +———+——-+————–+
          | Folders | Files | Elapsed time |
          +———+——-+————–+
          | 1506 | 3066 | 00:00:03 |
          +———+——-+————–+

          I’m wondering over (error) NOAUTH Authentication required. whats that?

          I have change the script a little because all of my shares is trough SMB but I only deleted this line from the script:
          sudo -u www-data php /var/www/nextcloud/occ files:scan –all

          • Robin says:

            I did also notice that you have deleted the row:
            5 1 * * * php -f /var/www/nextcloud/occ files:scan-app-data > /dev/null 2>&1

            why’s that?

          • it is part of the optimize.sh and in roots crontab now.

          • Robin says:

            I did find out the issue with the (error) NOAUTH Authentication required it’s because that I did use the hashsecret PW for redis, so I just added -a MYSECRETPASSWORD to the file and it did work after that.

          • i guess, you forgot sudo -s prior issuing the script or redis pwd.

          • Robin says:

            Okej, so I can delete this row now?
            5 1 * * * php -f /var/www/nextcloud/occ files:scan-app-data > /dev/null 2>&1

          • yes, and add the row from the guide to root’s crontab

  31. Cha'd says:

    Kindly help please, done all the setups without any error but from this step am getting “404 Not Found” Nginx

    Logon to your brandly new Nextcloud in your browser
    https://your.dedyn.io/login

    What i have missed?

    • Did you replace your.dedyn.io with your domain in the browser?

      • Cha'd says:

        Yes I did and i hav checked with local IP also it’s same. Have run the setup twice now all “https://your.dedyn.io/login” is replace by my nextcloud address.
        My loacal IP 192.168.x.221 router IP 192.168.x.201 which i set on “NGINX configuration(# resolver IP is your Router-IP (e.g. your FritzBox))” also did forward ports to reach outside my network and i can ping on my nextcloud address outside network.

        • please check the redis-, nginx-, php- and mysql logfiles. could you please issue curl https://your.dedyn.io and send me the result

          • Cha'd says:

            Here’s the out put of curl command, and i followed ur guide for ubuntu 16 it worked flawless but this one only giving problem. am not good with linux so didn’t no hot to check redis-, nginx-, php- and mysql logfiles.

            curl: (60) SSL certificate problem: self signed certificate
            More details here: https://curl.haxx.se/docs/sslcerts.html

            curl failed to verify the legitimacy of the server and therefore could not
            establish a secure connection to it. To learn more about this situation and
            how to fix it, please visit the web page mentioned above.

            Thank you for your support

          • Hi, please provide me ssh access to your environemnt per mail and i will investigate further. cheers, carsten

          • Cha'd says:

            Hi, done fresh installation Ubuntu server with static IP n configured all the steps but now “403 forbidden” nginx and below curl command.

            301 Moved Permanently

            301 Moved Permanently
            nginx

            Thank you

  32. aytac says:

    exelent tutorial only must be incluide onlyoffice or collabora so it will be completed 🙂

  33. robbie says:

    Hi Carsten. What modifications do you have to make to this if you decide to forward 80 and 443 to non traditional ports on your router? Eg 80 to 3000 and 443 to 5000?

  34. Miko says:

    Hallo, am Ende der zweiten Zeile bei “Install Nextcloud silently” ist ein kleiner Fehler, oder sind die Quotes am Ende richtig ?
    […]–data-dir “/var/nc_data”‘
    Auch wenn ich mir noch selbst einen anderen Fehler eingebaut habe, tolles howto.
    Vielen Dank

    Miko

  35. Stefan says:

    Hallo, In den Paketquellen von Ubuntu 18.04 ist nginx bereits in der Version 1.14 enthalten. Das Hinzufügen der nginx-Repositories zu den Paketquellen ist nicht daher nicht mehr erforderlich.
    Gruß Stefan

    • Leider doch, da sonst die NGINX Konfiguration für das buffering/streaming (MP4-Module) nicht verfügbar wäre. Trotzdem danke!. Servus, Carsten

      • Stefan says:

        Hallo, vielen Dank, an MP4 hatte ich nicht gedacht.
        Ansonsten TOP Anleitung.
        Verwende ngnix Reverse-Proxy vHost, NC und WordPress. Da hat sich was was geändert.
        Vielen Dank
        Gruß Stefan

        • servus. was hat sich geändert?

          • Stefan says:

            Die vHost-Dateien von nginx werden nun in den Verzeichnissen sites-available und sites-enabled verwaltet. vHost, die bis Dato unter /etc/nginx/conf.d gespeichert waren, müssen nun im Verzeichnis /etc/nginx/sitesd-enabled untergebracht werden.

          • Hallo Stefan. Man kann es so oder so machen, muss es aber nicht. Es hängt auch vom verwendeten NGINX ab. Danke u. servus, Carsten

  36. RubberChicken says:

    Awesome – Have been using OwnCloud on Ubuntu 14.04 but wanted to make the leap to Ubuntu 18.04 and NextCloud. Am not a strong Linux admin (have MS focus) but have been able to successfully build myself a new up to date and secure system thanks to your guide. Really appreciate it!

  37. Daniel Atz says:

    Very good!
    I do not know why, but my Nextcloud takes about 5 or 10 seconds to login. Ever.

    • please have a look into mysql:
      database nextcloud, table oc_bruteforce_attempts.
      If your client is listed issue
      delete from oc_bruteforce_attempts;
      to purge entries.

  38. Robin says:

    I have followed the guide and it works.

    But I have one issue now, when I’m looking under loggs in the Nextcloud WebGUI I can see this:

    The “X-XSS-Protection” HTTP header is not set to “1; mode=block”. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.
    The “X-Robots-Tag” HTTP header is not set to “none”. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.
    The “X-Download-Options” HTTP header is not set to “noopen”. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.
    The “X-Permitted-Cross-Domain-Policies” HTTP header is not set to “none”. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.

    But my /etc/nginx/header.conf are correct.

    # add_header Strict-Transport-Security “max-age=15768000; includeSubDomains; preload;”;
    add_header X-Robots-Tag none;
    add_header X-Download-Options noopen;
    add_header X-Permitted-Cross-Domain-Policies none;
    # add_header X-Content-Type-Options “nosniff” always;
    add_header X-XSS-Protection “1; mode=block” always;
    add_header Referrer-Policy “same-origin” always;

    And the header is included in the /etc/nginx/nginx.conf

  39. Robin says:

    And also I do get this:

    Apr 28 14:26:48 nextcloud nginx[4230]: nginx: [emerg] unknown directive “mp4” in /etc/nginx/conf.d/nextcloud.conf:41
    Apr 28 14:26:48 nextcloud nginx[4230]: nginx: configuration file /etc/nginx/nginx.conf test failed
    Apr 28 14:26:48 nextcloud systemd[1]: nginx.service: Control process exited, code=exited status=1
    Apr 28 14:26:48 nextcloud systemd[1]: nginx.service: Failed with result ‘exit-code’.
    Apr 28 14:26:48 nextcloud systemd[1]: Failed to start A high performance web server and a reverse proxy server.

  40. Robin says:

    Perfect thanks,
    But 18.04 still just have openssl 1.1.0g I guess that we still should install openssl 1.1.0h as that’s newer?

  1. 26. June 2018

    […] Wer gerne seine eigene Lösung zur Speicherung und zur gemeinsamen Verwendung von Daten verwenden möchte, kann hier mit Nextcloud auf eine ziemlich gute und kostenfreie Lösung zurückgreifen. Dieser Beitrag zeigt, wie ich die Installation und die Konfiguration vorgenommen habe. Als Vorlage für die Installation habe ich den folgenden, sehr guten und ausführlichen (DANKE dafür an dieser Stelle!) genutzt: Nextcloud 13 installation guide (Ubuntu 18.04 LTS) […]

  2. 5. July 2018

    […] you Carsten for your excellent guide!  My goal here is to base my walk-through on his but also mention the “Gotchas!!” that […]

Leave a Reply

Your email address will not be published. Required fields are marked *