Skip to content

This document is a WORK IN PROGRESS.
This is just a quick personal cheat sheet: treat its contents with caution!


Nextcloud (with PostgreSQL, Redis and Nginx) on Gentoo

Reference(s)

Table of contents


Install

Install Nextcloud:

# vi /etc/portage/package.accept_keywords
    > # (manual) last nextcloud
    > www-apps/nextcloud
    >
    > ...
# vi /etc/portage/package.use/nextcloud
    > ...
    > www-apps/nextcloud -sqlite
    > www-apps/nextcloud postgres
    > www-apps/nextcloud vhosts
    > ...
# emerge -a nexcloud

# webapp-config -h yourhostname.duckdns.org -d nextcloud -I nextcloud xx.x.x
# chown -R nginx:nginx /var/www/yourhostname.duckdns.org

Then make sure that all prerequisites packages are installed and configured.


Nextcloud packages in world set

Make sure your current major Nextcloud packages won't be overwritten by accident in an upcoming update (e.g. with those packages at those versions):

# emerge -av --noreplace www-apps/nextcloud:18.0.0
# emerge -av --noreplace dev-lang/php:7.3
?# emerge -av --noreplace www-servers/nginx:1.16.1-r1
?# emerge -av --noreplace dev-db/redis:5.0.7
# emerge -av --noreplace dev-db/postgresql:12

# vi /var/lib/portage/world # check your current nextcloud packages versions in the world file:
    > ...
    > www-apps/nextcloud
    > www-apps/nextcloud:18.0.0
    > ...
    > dev-lang/php
    > dev-lang/php:7.3
    > ...
    > www-servers/nginx
    > www-servers/nginx:1.16.1-r1
    > ...
    > dev-db/redis
    > dev-db/redis:5.0.7
    > ...
    > dev-db/postgresql
    > dev-db/postgresql:12
    > ...

After properly updating one of those packages (see "Nextcloud update procedure" section near the end of this file), don't forget to update the /var/lib/portage/world file accordingly.

Domain name and router configuration

Warning

Getting a domain name is optional, but recommended if you don't want to enter your server's public IP address every time you want to access your Nextcloud server, or if you don't have a fixed public IP address.

Warning

Configuring your router in order to forward your port 80 and port 443 is optional, but recommended if you want to access your Nextcloud server outside your local network.

You can refer to DuckDNS for a free and open source service which will point a DNS (sub domains of ) to an IP of your choice, but you can choose the domain name provider of your choice. In any cases, this cheat sheet will assume that your domain name is yourhostname.duckdns.org

Tip

You might want to check if your router does support NAT loopback (see https://en.wikipedia.org/wiki/Network_address_translation#NAT_hairpinning and see https://en.wikipedia.org/wiki/Hairpinning). If it doesn't, you will have to look for a work around in order to access your server by it's domain name from your local network.


Config

PostgreSQL Configuration

Create the Nextcloud user and Nextcloud database:

$ su - postgres # enter postgres pwd
$ createuser -h localhost -P nextcloud # enter nextcloud pwd, confirm pwd & enter postgres pwd
$ createdb -O nextcloud nextcloud # and enter postgres pwd
$ exit
$ exit

TODO: PostgreSQL optimization:

APCu and Redis config (Memory caching config)

Reference(s)

Configure Redis:

# cp /etc/redis.conf /etc/redis.conf.bak
# vi /etc/redis.conf
    > ...
    > #port 6379
    > port 0
    > ...
    > # unixsocket /tmp/redis.sock
    > # unixsocketperm 700
    >
    > # Enable and update the Redis socket path:
    > unixsocket /run/redis/redis.sock
    > # Set permission to all members of the redis user group:
    > unixsocketperm 770
    > ...
    > #maxclients 10000
    > maxclients 512
    > ...

Add the Nginx user to the Redis group (Nginx should logout and log back in for this change to takes effect, or just reboot):

$ sudo usermod -aG redis nginx

Add APCu support for user cache:

# emerge -a dev-php/pecl-apcu

Make sure you are building pecl-apcu against the right PHP version:

$ php --version
$ equery uses pecl-apcu
$ php -m | grep apcu

If pecl-apcu is not built against the right PHP version (e.g. v7.2 instead of v7.3):

$ sudo vi /etc/portage/package.use/pecl
    > dev-php/pecl-apcu -php_targets_php7-2
    > dev-php/pecl-apcu php_targets_php7-3
$ sudo emerge -a dev-php/pecl-apcu

Enable APC in php.ini:

# vi /etc/php/fpm-phpX.X/php.ini

    > ...
    > ; Misc
    >
    > ; APCu is disabled by default on CLI which could cause issues with nextcloud’s
    > ; cron jobs. Enabling it:
    > apc.enable_cli = 1

Configure Nextcloud to use Redis:

# vi /var/www/yourhostname.duckdns.org/htdocs/nextcloud/config/config.php
    > ...
    > 'memcache.local' => '\OC\Memcache\APCu', # and not '\\OC\\Memcache\\APCu' ?
    > 'memcache.locking' => '\OC\Memcache\Redis', # and not '\\OC\\Memcache\\Redis' ?
    > 'redis' => [
    >      'host'     => '/var/run/redis/redis.sock',
    >      'port'     => 0,
    > ],

Optimize Redis:

# vi /etc/sysctl.conf
    > ...
    > # Fix Redis low memory condition warning:
    > vm.overcommit_memory = 1
    > # Fix Redis TCP backlog setting warning:
    > net.core.somaxconn=65535

Nginx config

Configure Nginx according to the Nextcloud manual: https://docs.nextcloud.com/server/stable/admin_manual/installation/nginx.html.

For example, here is my /etc/nginx/nginx.conf (default configuration):

    > user nginx nginx;
    > worker_processes auto;
    >
    > error_log /var/log/nginx/error_log info;
    >
    > events {
    >   worker_connections 1024;
    >   use epoll;
    > }
    >
    > http {
    >   include /etc/nginx/mime.types;
    >   default_type application/octet-stream;
    >
    >   log_format main
    >       '$remote_addr - $remote_user [$time_local] '
    >       '"$request" $status $bytes_sent '
    >       '"$http_referer" "$http_user_agent" '
    >       '"$gzip_ratio"';
    >
    >   client_header_timeout 10m;
    >   client_body_timeout 10m;
    >   send_timeout 10m;
    >
    >   connection_pool_size 256;
    >   client_header_buffer_size 1k;
    >   large_client_header_buffers 4 2k;
    >   request_pool_size 4k;
    >
    >   gzip off;
    >
    >   output_buffers 1 32k;
    >   postpone_output 1460;
    >
    >   sendfile on;
    >   tcp_nopush on;
    >   tcp_nodelay on;
    >
    >   keepalive_timeout 75 20;
    >
    >   ignore_invalid_headers on;
    >
    >   index index.html;
    >
    >   #include /etc/nginx/conf.d/*.conf;
    >   include /etc/nginx/conf.d/nextcloud.conf;
    > }

And here is my /etc/nginx/conf.d/nextcloud.conf (default configuration):

    > # https://docs.nextcloud.com/server/stable/admin_manual/installation/nginx.html?highlight=nginx
    >
    > upstream php-handler {
    >     server 127.0.0.1:9000;
    >     #server unix:/var/run/php-fpm/php-fpm.sock;
    > }
    >
    > server {
    >     listen 80;
    >     listen [::]:80;
    >     server_name yourhostname.duckdns.org;
    >     # enforce https
    >     return 301 https://$server_name:443$request_uri;
    > }
    >
    > server {
    >     listen 443 ssl http2;
    >     listen [::]:443 ssl http2;
    >     server_name yourhostname.duckdns.org;
    >
    >     # Use Mozilla's guidelines for SSL/TLS settings
    >     # https://mozilla.github.io/server-side-tls/ssl-config-generator/
    >     # NOTE: some settings below might be redundant
    >
    >     # ssl_certificate /etc/ssl/path/to/your/cert.crt
    >     # ssl_certificate_key /etc/ssl/path/to/your/key.key
    >
    >     # Add headers to serve security related headers
    >     # Before enabling Strict-Transport-Security headers please read into this
    >     # topic first.
    >     #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
    >     #
    >     # WARNING: Only add the preload option once you read about
    >     # the consequences in https://hstspreload.org/. This option
    >     # will add the domain to a hardcoded list that is shipped
    >     # in all major browsers and getting removed from this list
    >     # could take several months.
    >     add_header X-Content-Type-Options nosniff;
    >     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;
    >     add_header Referrer-Policy no-referrer;
    >     add_header X-Frame-Options "SAMEORIGIN";
    >
    >     # Remove X-Powered-By, which is an information leak
    >     fastcgi_hide_header X-Powered-By;
    >
    >     # Path to the root of your installation
    >     root /var/www/yourhostname.duckdns.org/htdocs/nextcloud/;
    >
    >     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;
    >
    >     # The following rule is only needed for the Social app.
    >     # Uncomment it if you're planning to use this app.
    >     #rewrite ^/.well-known/webfinger /public.php?service=webfinger last;
    >
    >     location = /.well-known/carddav {
    >       return 301 $scheme://$host:$server_port/remote.php/dav;
    >     }
    >     location = /.well-known/caldav {
    >       return 301 $scheme://$host:$server_port/remote.php/dav;
    >     }
    >
    >     # set max upload size
    >     client_max_body_size 512M;
    >     fastcgi_buffers 64 4K;
    >
    >     # Enable gzip but do not remove ETag headers
    >     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/manif    est+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf applic    ation/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 tex    t/x-cross-domain-policy;
    >
    >     # Uncomment if your server is build with the ngx_pagespeed module
    >     # This module is currently not supported.
    >     #pagespeed off;
    >
    >     location / {
    >         rewrite ^ /index.php$request_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\/.+|oc[ms]-provider\/.    +)\.php(?:$|\/) {
    >         fastcgi_split_path_info ^(.+?\.php)(\/.*|)$;
    >         include fastcgi_params;
    >         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;
    >         # Enable pretty urls
    >         fastcgi_param front_controller_active true;
    > fastcgi_pass php-handler;
    >         fastcgi_intercept_errors on;
    >         fastcgi_request_buffering off;
    >     }
    >
    >     location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) {
    >         try_files $uri/ =404;
    >         index index.php;
    >     }
    >
    >     # Adding the cache control header for js, css and map files
    >     # Make sure it is BELOW the PHP block
    >     location ~ \.(?:css|js|woff2?|svg|gif|map)$ {
    >         try_files $uri /index.php$request_uri;
    >         add_header Cache-Control "public, max-age=15778463";
    >         # 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;";
    >         #
    >         # WARNING: Only add the preload option once you read about
    >         # the consequences in https://hstspreload.org/. This option
    >         # will add the domain to a hardcoded list that is shipped
    >         # in all major browsers and getting removed from this list
    >         # could take several months.
    >         add_header X-Content-Type-Options nosniff;
    >         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;
    >         add_header Referrer-Policy no-referrer;
    >         add_header X-Frame-Options "SAMEORIGIN";
    >
    >         # Optional: Don't log access to assets
    >         access_log off;
    >     }
    >
    >     location ~ \.(?:png|html|ttf|ico|jpg|jpeg)$ {
    >         try_files $uri /index.php$request_uri;
    >         # Optional: Don't log access to other assets
    >         access_log off;
    >     }
    > }

php-pfm config

Configure php-pfm in order to work with the environment variables and Nginx:

# vi /etc/php/fpm-phpx.x/fpm.d/www.conf # uncomment env variables:
    > ...
    > ; Unix user/group of processes
    > ; Note: The user is mandatory. If the group is not set, the default user's group
    > ;       will be used.
    > user = nginx
    > group = nginx
    >...
    > env[HOSTNAME] = $HOSTNAME
    > env[PATH] = /usr/local/bin:/usr/bin:/bin
    > env[TMP] = /tmp
    > env[TMPDIR] = /tmp
    > env[TEMP] = /tmp
    > ...

Increase the PHP memory limit:

# vi /etc/php/fpm-phpX.X/php.ini
    > ; Maximum amount of memory a script may consume (128MB)
    > ; http://php.net/memory-limit
    > memory_limit = 2048M

Enable OPcache:

# vi /etc/php/fpm-phpX.X/php.ini
    > ...
    > [opcache]
    > ; Determines if Zend OPCache is enabled
    > opcache.enable=1
    >
    > ; Determines if Zend OPCache is enabled for the CLI version of PHP
    > opcache.enable_cli=1
    >
    > ; The OPcache shared memory storage size.
    > ;opcache.memory_consumption=128
    >
    > ; The amount of memory for interned strings in Mbytes.
    > opcache.interned_strings_buffer=8
    >
    > ; The maximum number of keys (scripts) in the OPcache hash table.
    > ; Only numbers between 200 and 1000000 are allowed.
    > opcache.max_accelerated_files=10000
    >
    > ; The maximum percentage of "wasted" memory until a restart is scheduled.
    > ;opcache.max_wasted_percentage=5
    >
    > ; When this directive is enabled, the OPcache appends the current working
    > ; directory to the script key, thus eliminating possible collisions between
    > ; files with the same name (basename). Disabling the directive improves
    > ; performance, but may break existing applications.
    > ;opcache.use_cwd=1
    >
    > ; When disabled, you must reset the OPcache manually or restart the
    > ; webserver for changes to the filesystem to take effect.
    > ;opcache.validate_timestamps=1
    >
    > ; How often (in seconds) to check file timestamps for changes to the shared
    > ; memory storage allocation. ("1" means validate once per second, but only
    > ; once per request. "0" means always validate)
    > opcache.revalidate_freq=1
    >
    > ; Enables or disables file search in include_path optimization
    > ;opcache.revalidate_path=0
    >
    > ; If disabled, all PHPDoc comments are dropped from the code to reduce the
    > ; size of the optimized code.
    > opcache.save_comments=1
    > ...

Install PHP extension for interfacing with Redis:

# emerge -a dev-php/pecl-redis

Install imagick recommended PHP modules (for improved performance and better compatibility), and make sure it's built against the right PHP version:

# emerge -a dev-php/pecl-imagick
$ equery uses pecl-imagick
$ php --version

If pecl-imagick is not build against the right version of PHP (e.g. 7.2 instead of 7.3):

$ sudo vi /etc/portage/package.use/pecl
    > ...
    > dev-php/pecl-imagick -php_targets_php7-2
    > dev-php/pecl-imagick php_targets_php7-3

# emerge -a dev-php/pecl-imagick

# rc-update add php-fpm default

Basic SSL certs config

Create basic self signed SSL certificate and key:

# mkdir /etc/ssl/nextcloud
# openssl req -x509 -nodes -days 9999 -newkey rsa:2048 -keyout /etc/ssl/nextcloud/yourhostname.key -out /etc/ssl/nextcloud/yourhostname.crt

Add the SSL cert and key to the Nginx Nextcloud configuration:

# vi /etc/nginx/conf.d/nextcloud.conf
    > ...
    > # Use Mozilla's guidelines for SSL/TLS settings
    > # https://mozilla.github.io/server-side-tls/ssl-config-generator/
    > # NOTE: some settings below might be redundant
    >
    > ssl_certificate /etc/ssl/nextcloud/yourhostname.crt;
    > ssl_certificate_key /etc/ssl/nextcloud/yourhostname.key;
    > ...

Test your configuration:

# nginx -t # test the nginx config
# rc-service postgresql-x.x restart
# rc-service nginx restart
# rc-service php-fpm restart

Now open a web browser and navigate to www.yourhostname.duckdns.org.

Advanced SSL certs config

Install acme and request ssl-certificates:

# emerge -a acme-tiny
# mkdir /var/www/yourhostname.duckdns.org/acme-challenge/
# chown -R nginx:nginx /var/www/yourhostname.duckdns.org/acme-challenge

# mkdir /var/lib/letsencrypt
# cd /var/lib/letsencrypt
# openssl genrsa 4096 > account.key
# openssl genrsa 4096 > domain.key
# openssl req -new -sha256 -key domain.key -subj "/CN=yourhostname.duckdns.org" > domain.csr

# vi /etc/nginx/conf.d/nextcloud.conf # add acm-challenge location and turn off https
    > ...
    > # server {
    > #     listen 80;
    > #     listen [::]:80;
    > #     server_name yourhostname.duckdns.org;
    > #
    > #     # enforce https
    > #     return 301 https://$server_name:443$request_uri;
    > # }
    >
    > server {
    > #    listen 443 ssl http2;
    > #    listen [::]:443 ssl http2;
    >     listen 80;
    >     listen [::]:80;
    >     server_name yourhostname.duckdns.org;
    >
    >     location ^~ /.well-known/acme-challenge/ {
    >         alias /var/www/yourhostname.duckdns.org/acme-challenge/;
    >         try_files $uri =404;
    >     }
    > ...


# rc-service nginx restart

$ sudo -i
# acme-tiny --account-key /var/lib/letsencrypt/account.key --csr /var/lib/letsencrypt/domain.csr --acme-dir /var/www/yourhostname.duckdns.org/acme-challenge/ > /var/lib/letsencrypt/signed_chain.crt
# exit

# cp /var/lib/letsencrypt/signed_chain.crt /var/lib/letsencrypt/signed_chain.crt.back # backup signed_chain.crt just in case

# openssl dhparam -out dhparam4096.pem 4096

# vi /etc/nginx/conf.d/nextcloud.conf # turn https back on and ssl certs
    > ...
    > server {
    >     listen 80;
    >     listen [::]:80;
    >     server_name yourhostname.duckdns.org;
    >
    >     location ^~ /.well-known/acme-challenge/ {
    >         alias /var/www/yourhostname.duckdns.org/acme-challenge/;
    >         try_files $uri =404;
    >     }
    >
    >     # enforce https
    >     return 301 https://$server_name:443$request_uri;
    > }
    >
    > server {
    >     listen 443 ssl http2;
    >     listen [::]:443 ssl http2;
    >
    >     # Use Mozilla's guidelines for SSL/TLS settings
    >     # https://mozilla.github.io/server-side-tls/ssl-config-generator/
    >     # NOTE: some settings below might be redundant
    >
    >     #ssl_certificate /etc/ssl/nextcloud/yourhostname.crt;
    >     #ssl_certificate_key /etc/ssl/nextcloud/yourhostname.key;
    >
    >     ssl_certificate /var/lib/letsencrypt/signed_chain.crt;
    >     ssl_certificate_key /var/lib/letsencrypt/domain.key;
    >
    >     ssl_session_timeout 1d;
    >     ssl_session_cache shared:MozSSL:10m;
    >     ssl_session_tickets off;
    >
    >     ssl_dhparam /var/lib/letsencrypt/dhparam4096.pem;
    >
    >     ssl_protocols TLSv1.2 TLSv1.3;
    >     ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA;
    >     ssl_prefer_server_cipher on;
    >
    >     # Add headers to serve security related headers
    >     # Before enabling Strict-Transport-Security headers please read into this
    >     # topic first.
    >     #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
    >
    >     add_header Strict-Transport-Security "max-age=15768000; includeSubDomains" always;
    >
    >     ...
    >
    >      # Adding the cache control header for js, css and map files
    >      # Make sure it is BELOW the PHP block
    >      location ~ \.(?:css|js|woff2?|svg|gif|map)$ {
    >          try_files $uri /index.php$request_uri;
    >          add_header Cache-Control "public, max-age=15778463";
    >          # 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 Strict-Transport-Security "max-age=15768000; includeSubDomains" always;
    > ...

# rc-service nginx restart
Now test your config at this addresses:

With the previous configurations, you should get A+ grades.

Auto renew certificates Cron job config

# vi /opt/renew_cert.sh
    > #!/bin/sh
    >
    > rm -f /var/lib/letsencrypt/signed_chain.crt.tmp
    >
    > acme-tiny --disable-check --account-key /var/lib/letsencrypt/account.key --csr /var/lib/letsencrypt/domain.csr --acme-dir /var/www/yourhostname.duckdns.org/acme-challenge/ > /var/lib/letsencrypt/signed_chain.crt.tmp || exit
    >
    > mv -f /var/lib/letsencrypt/signed_chain.crt.tmp /var/lib/letsencrypt/signed_chain.crt
    > rc-service nginx reload

# chmod 700 /opt/renew_cert.sh

[!] Note that this script won't work if the current certificate is already expired! [!]

$ sudo EDITOR=vi crontab -e # edit the crontab of the root user
    > ...
    > 0 0 1 * * /opt/renew_cert.sh 2>> /var/log/acme-tiny.log # runs once per month

Initialize Nextcloud

Navigate to youhostname.duckdns.org in a browser. The first time, you will have a few fields to configure:

  • admin account name: admin
  • admin account password: adminpwd

  • data folder: /media/raid/nextcloud-data

  • db user name: nextcloud

  • db user password (see Nextcloud user): nextcloudpwd
  • db name: nextcloud
  • db location (default port of PostgreSQL): localhost:5432

Now Nextcloud is initializing and will soon be accessible.


Logs

Make sure logs are active, if there is no /var/log/nextcloud.log file, run:

# touch /var/log/nextcloud.log
# chown nginx:nginx /var/log/nextcloud.log

# vi /var/www/gentoocloud.duckdns.org/htdocs/nextcloud/config/config.php

    > <?php
    > $CONFIG = array (
    > ...
    >
    > 'logfile' => '/var/log/nextcloud.log',
    > 'loglevel' => 1,
    > 'logtimezone' => 'Europe/Paris',
    > 'log_rotate_size' => 104857600,
    >
    > ...
    > );


fail2ban

Create a fail2ban Nextcloud filter:

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

    > [Definition]
    > failregex=^{"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>'\)".*}$
    >             ^.*\"remoteAddr\":\"<HOST>\".*Trusted domain error.*$

Create a new jail:

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

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

Restart the fail2ban service:

# rc-service fail2ban restart
# fail2ban-client status nextcloud


Cron

Nextcloud requires scheduled execution of some tasks, and by default it archives this by using AJAX, however AJAX is the least reliable method, and it is recommended to use Cron instead.

Create a job for the Nginx user:

# crontab -u nginx -e
    > */5  *  *  *  * php -f /var/www/yourhostname.duckdns.org/htdocs/nextcloud/cron.php

Check that everything is set by running:

# crontab -u nginx -l


! WIP ! rsync backup script to run with Cron

This script will backup your Nextcloud data directory in /media/backup/nextcloud-backup (if available, i.e. if a disk is mounted on /media/backup):


TODO


External storages

One can add external storages to Nextcloud. For example, a local folder that might be shared between the server and Nextcloud.

First create a directory on your server that will be shared with Nextcloud, e.g.:

$ mkdir /media/raid/nextcloud-media

Then create a local external storage in Nextcloud. To do so:

  • connect to the Nextcloud web client (with admin permissions)
  • open the top right user menu
  • select "Apps"
  • select "Disabled apps" in the left menu
  • enable the "External storages" app
  • open the top right user menu again
  • select "Settings"
  • select "External storages" in the left menu
  • finally create a local external storage (e.g. Folder name: nextcloud-media, External storage: "Local", Authentication: "None", Configuration: /media/raid/nextcloud-media, Available for: "All users..."

Configure a VPN to accept traffic from public IP and respond on the same channel

See ProtonVPN cheat sheet (this tips should work with most VPNs, not only ProtonVPN).

NOTE: if the following error shows up: PR_END_OF_FILE_ERROR, when trying to access your Nextcloud instance, just restart it.


davfs2

You can mount your Nextcloud using WebDAV with davfs2.

E.g.:

$ sudo mount -t davfs https://yourhostname.duckdns.org/remote.php/dav/files/NextUserName/ ~/nextcloud/NextUserName -o uid="linux-username",gid="linux-username",rw

Install, config and use

See https://docs.nextcloud.com/server/18/user_manual/files/access_webdav.html

A correct kernel config is needed:

    > -> File systems
    >     -> Network File Systems # Symbol: NETWORK_FILESYSTEMS [=y])
    >         <*>   Coda file system support (advanced network fs) # Symbol: CODA_FS [=y]

Warning

After configuring the kernel don't forget to do a kernel make and rebuild!

Install davfs2:

# emerge -a net-fs/davfs2

Add your user to the davfs2 group:

$ sudo gpasswd -a your-user-name davfs2
Logout and log back in.


Now you can mount your Nextcloud with davfs:

$ mkdir -p ~/dav
$ sudo mount -t davfs https://yourhostname.duckdns.org/remote.php/dav/files/Nextcloud-User-Name/ ~/dav -o uid="linux-user",gid="linux-user",rw


ufw

See ufw

TODO


! WIP ! Import Bitwarden passwords into Nextcloud Passwords app

In your Bitwarden settings -> "export vault" -> .csv -> "submit" You should now have a bitwarden_export_date.csv file.

The Nextcloud escape character will be \ (backslash), but there is no such thing like escape character in the Bitwarden CSV, so one shall escape every backslash that is already in the bitwarden_export_date.csv file (with another backslash):

:%s/\\/\\\\/g

The quote character will be "'" (simple quote), but there no such thing like quote character in the Bitwarden CSV, so one shall escape every quote character (with a backslash):

:%s/\'/\\\'/g
The default Bitwarden field delimiter is the comma "," and Bitwarden does not escape the field delimiter, so one might have to unescaped field delimiter in passwords.

The following Vim commands will escape the delimiters contained in passwords:

:%s/.*\zs,/TEMP_FIELD_DELIMITER/ " replace every last ',' of each line by a temp delimiter
:%s/,/TEMP_FIELD_DELIMITER/ "
7@:                         " replace every 8 first ',' of each line by a temp delimiter
                            " because by default there should be 9 delimiters per line
:%s/,/\\,/g " escape every remaining ',' with a backslash in order to avoid
:%s/TEMP_FIELD_DELIMITER/,/g " set the new delimiter to ','

FIX ME: I just realized that there might be unescaped delimiter in notes too, not just passwords...

FIX ME: So I think would better write a real script to solve this...

In Nextcloud Passwords app -> "More" (`+` button) -> "Backup and Restore" -> "Restore or Import"
    -> "1. Choose Format: Other/Custom CSV"
    -> "2. Select File:
        CSV Options
        Line Break: Windows (CRLF, \r\n)
        Field Delimiter: Detect
        Quote Character: Single Quote
        Escpae Character: Backslash"
    -> Do not check "Detect unescaped quotes"
    -> "Browse" and select the bitwarden_export_date.csv file
    -> "3. Select Options
        ...

Procedures

Nextcloud trigger maintenance

$ sudo -u nginx php /var/www/yourhostname.duckdns.org/htdocs/nextcloud/occ maintenance:mode --on
$ sudo -u nginx php /var/www/yourhostname.duckdns.org/htdocs/nextcloud/occ maintenance:mode --off

Nextcloud stop procedure

# rc-service nginx stop && rc-service php-fpm stop && rc-service redis stop && rc-service postgresql-xx stop

Nextcloud backup procedure

Nextcloud backup config:

# rsync -aAXv /var/www/yourhostname.duckdns.org /media/backup/nextcloud-backup/config_`date +"%Y%m%d"`/

PostgreSQL backup database (https://www.postgresql.org/docs/xx/backup.html):

$ sudo -u postgres pg_dump nextcloud | sudo tee pg_dump_`date +"%Y%m%d"`

Nextcloud data backup:

# rsync -aAXv /media/raid/nextcloud-data /media/backup/nextcloud-backup/data_`date +"%Y%m%d"`/
# rsync -aAXv /media/raid/nextcloud-media /media/backup/nextcloud-backup/media_`date +"%Y%m%d"`/

Nextcloud restore procedure

TODO

Nextcloud change data directory procedure

$ sudo -u nginx php /var/www/yourhostname.duckdns.org/htdocs/nextcloud/occ maintenance:mode --on

$ sudo cp -r /path-to-old-directory/nextcloud-data /path-to-new-directory/nextcloud-data
$ sudo vi /var/www/yourhostname.duckdns.org/htdocs/nextcloud/config/config.php

    > 'datadirectory' => '/path-to-new-directory/nextcloud-data',

$ sudo chown -R nginx /path-to-new-directory/nextcloud-data
$ sudo -u nginx php /var/www/yourhostname.duckdns.org/htdocs/nextcloud/occ files:scan --all

$ sudo -u nginx php /var/www/yourhostname.duckdns.org/htdocs/nextcloud/occ maintenance:mode --off

Nextcloud direct files copy procedure

$ sudo cp -r /path-to-files/files-folder /path-to-data/nextcloud-data/user/files/files-folder
$ sudo chown -R nginx /path-to-data/nextcloud-data
$ sudo -u nginx php /var/www/yourhostname.duckdns.org/htdocs/nextcloud/occ files:scan --all

Nextcloud restart procedure

# nginx -t && rc-service nginx stop && rc-service php-fpm restart && rc-service redis restart && rc-service postgresql-xx restart && redis-cli -s /run/redis/redis.sock
    > FLUSHALL
    > quit
# rc-service nginx start

Nextcloud restore advanced SSL certs config

If your server was down for too long, your "auto renew certificates" script (run Cron job) may fail. In this case you will have to manually restore them:

# vi /etc/nginx/conf.d/nextcloud.conf # add acm-challenge location and turn off https
   > ...
 ~ > # server {
 ~ > #     listen 80;
 ~ > #     listen [::]:80;
 ~ > #     server_name yourhostname.duckdns.org;
 ~ > #
 ~ > #     # enforce https
 ~ > #     return 301 https://$server_name:443$request_uri;
 ~ > # }
   >
   > server {
 ~ > #    listen 443 ssl http2;
 ~ > #    listen [::]:443 ssl http2;
 + >
 + >     listen 80;
 + >     listen [::]:80;
 + >     server_name yourhostname.duckdns.org;
 + >
 + >     location ^~ /.well-known/acme-challenge/ {
 + >         alias /var/www/yourhostname.duckdns.org/acme-challenge/;
 + >         try_files $uri =404;
 + >     }
   >
   >     # Use Mozilla's guidelines for SSL/TLS settings
   >     # https://mozilla.github.io/server-side-tls/ssl-config-generator/
   >     # NOTE: some settings below might be redundant
   >
 ~ >     ssl_certificate /etc/ssl/nextcloud/yourhostname.crt;
 ~ >     ssl_certificate_key /etc/ssl/nextcloud/yourhostname.key;
   >
 ~ > #   ssl_certificate /var/lib/letsencrypt/signed_chain.crt;
 ~ > #   ssl_certificate_key /var/lib/letsencrypt/domain.key;
   >
 ~ > #   ssl_session_timeout 1d;
 ~ > #   ssl_session_cache shared:MozSSL:10m;
 ~ > #   ssl_session_tickets off;
   >
 ~ > #   ssl_dhparam /var/lib/letsencrypt/dhparam4096.pem;
 ~ >
 ~ > #   ssl_protocols TLSv1.2 TLSv1.3;
 ~ > #   ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
 ~ > #   ssl_prefer_server_ciphers on;
   > ...
 ~ > #   add_header Strict-Transport-Security "max-age=15768000; includeSubDomains" always;
   > ...
 ~ > #       add_header Strict-Transport-Security "max-age=15768000; includeSubDomains" always;
   > ...

# rc-service nginx restart

$ sudo -i
# acme-tiny --account-key /var/lib/letsencrypt/account.key --csr /var/lib/letsencrypt/domain.csr --acme-dir /var/www/yourhostname.duckdns.org/acme-challenge/ > /var/lib/letsencrypt/signed_chain.crt
# exit

# cp /var/lib/letsencrypt/signed_chain.crt /var/lib/letsencrypt/signed_chain.crt.back # backup signed_chain.crt just in case

# vi /etc/nginx/conf.d/nextcloud.conf # turn https back on and ssl certs
   > ...
   > server {
   >     listen 80;
   >     listen [::]:80;
   >     server_name yourhostname.duckdns.org;
   >
   >     location ^~ /.well-known/acme-challenge/ {
   >         alias /var/www/yourhostname.duckdns.org/acme-challenge/;
   >         try_files $uri =404;
   >     }
   >
   >     # enforce https
   >     return 301 https://$server_name:443$request_uri;
   > }
   >
   > server {
   >     listen 443 ssl http2;
   >     listen [::]:443 ssl http2;
   >
   >     # Use Mozilla's guidelines for SSL/TLS settings
   >     # https://mozilla.github.io/server-side-tls/ssl-config-generator/
   >     # NOTE: some settings below might be redundant
   >
   >     #ssl_certificate /etc/ssl/nextcloud/yourhostname.crt;
   >     #ssl_certificate_key /etc/ssl/nextcloud/yourhostname.key;
   >
   >     ssl_certificate /var/lib/letsencrypt/signed_chain.crt;
   >     ssl_certificate_key /var/lib/letsencrypt/domain.key;
   >
   >     ssl_session_timeout 1d;
   >     ssl_session_cache shared:MozSSL:10m;
   >     ssl_session_tickets off;
   >
   >     ssl_dhparam /var/lib/letsencrypt/dhparam4096.pem;
   >
   >     ssl_protocols TLSv1.2 TLSv1.3;
   >     ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA;
   >     ssl_prefer_server_cipher on;
   >
   >     ...
   >
   >     add_header Strict-Transport-Security "max-age=15768000; includeSubDomains" always;
   >
   >     ...
   >
   >          add_header Strict-Transport-Security "max-age=15768000; includeSubDomains" always;
   > ...

# rc-service nginx restart

Nextcloud update procedure

  • ! Stop Nextcloud (see Nextcloud stop procedure) and backup everything (see Nextcloud backup procedure) !

  • ! Update your "Nextcloud packages" (Nextcloud, Nginx, Redis, php-fpm and PostgreSQL) on by one

DO NOT UPDATE THEM ALL AT THE SAME TIME !

  • /var/www/yourhostname.duckdns.org/htdocs/nextcloud/config/config.php might have to be changed back after a Nextcloud update

Nextcloud package

# emerge -uD nextcloud
# webapp-config --upgrade nextcloud 42.0.2 --host yourhostname.duckdns.org # e.g. update to 42.0.0

PHP package

See PHP.

After the update you might have to reproduce ?every? PHP related steps of this guide.

Redis package

See Redis.

After the update you might have to reproduce ?every? Redis related steps of this guide.

Nginx package

See Nginx.

After the update you might have to reproduce ?every? Nginx related steps of this guide.

PostgreSQL package

See PostgreSQL.

After the update you might have to reproduce ?every? PostgreSQL related steps of this guide.

Restart

After properly updating one of those packages, don't forget to update the /var/lib/portage/world file accordingly if needed.

Finally, restart Nextcloud (see Nextcloud restart procedure).


Troubleshooting

  • check services (make sure they are running, not stopped nor crashed):

    $ rc-status --all
    

    • if one of those is not working correctly, try to run it manually and see what happens, e.g with Redis:
      # rc-service redis stop
      # sudo -u redis redis-server /etc/redis.conf
      ...
      
  • check the logs :

  • check that the occ command is working well (if it doesn't, look at what it shoot):

    $ sudo -u nginx php /var/www/yourhostname.duckdns.org/htdocs/nextcloud/occ
    

Secure connection failed and Firefox did not connect

If you get this error, this is most likely because your certificate expired...

Server not accessible inside local network (but accessible from outside)

Curl error 28 - app store blank - no apps shown - request timeout

> Change line 404 in 3rdparty/guzzlehttp/guzzle/src/Handler/CurlFactory.php

> increase 1000 to 10000

> In lib/private/App/AppStore/Fetcher/Fetcher.php

> On line 98 change the timeout from 10 to 30 or 90

> in lib/private/Http/Client.php [now lib/private/Http/Client.php]

> On line 66 change the timeout from 30 to 90 [now line 71]

> These changes migh work for you if your connection is slow. They will also invalidate the code
> checks so Nextcloud will complain on the update page.

> They will also disapear when you upgrade Nextcloud to a new version.

TODO


If this cheat sheet has been useful to you, then please consider leaving a star here.