Install Nextcloud on Arch Linux
Comment
For some reason the <source> tags don't work, will fix it later.
Introduction
This document describes how to set up Nextcloud on a virtual private server (VPS) hosted by a cloud service provider that provides images for Arch Linux. We assume there is a DNS record in place that points the address cloud.mydomain.net
to the IP address provided by the cloud service provider.
Basic setup Arch Linux
Initial setup
Since the system comes preinstalled with Arch Linux on it, the first thing to do is to update the system:
<source lang="bash">pacman -Syu
reboot
pacman -S base base-devel </source>
Add an administration user
We add a day-to-day administrator account:
<source lang="bash">useradd -m -g users -s /bin/bash admin
passwd admin
</source>
Make sure that pacman
can be executed as user admin
:
<source lang="bash">visudo </source> And add the following:
<source lang="bash">admin ALL=(ALL) NOPASSWD: /usr/bin/pacman </source>
Create a swap partition
The swap partition has already been activated, so there is nothing to do.
Configure the language and locales
In file /etc/locale.gen
:
<source lang="bash">en_US.UTF-8 UTF-8 </source> Then execute:
<source lang="bash">locale-gen
echo LANG=en_US.UTF-8 > /etc/locale.conf export LANG=en_US.UTF-8 </source> Add the correct time zone, for example:
ln -s /usr/share/zoneinfo/Europe/Amsterdam /etc/localtime
Time configuration
Since Arch sits in a virtual machine, we assume that the time is always correct.
Random number generation
Random number generation is important for security, so we install the following tools:
<source lang="bash">pacman -S rng-tools
systemctl start rngd systemctl enable rngd </source> To check the level of entropy we can do:
<source lang="bash">cat /proc/sys/kernel/random/entropy_available </source> Since it runs on a virtual machine, we are not really sure how representative the entropy is.
We can also run the following tests which should have very few failures:
<source lang="bash">cat /dev/random | rngtest -c 1000
cat /dev/urandom | rngtest -c 1000
</source>
The rng-tools
service is preferred over haveged
.
Setting the hostname
Assuming we have a hostname mydomainname.net
that points to our server's IP address:
In /etc/hostname
:
<source lang="bash">cloud.mydomainname.net
</source>
In /etc/hosts
:
<source lang="bash">127.0.1.1 cloud.mydomainname.net
</source>
After a reboot, we can check whether everything is set with the command hostname -f
.
Setting up the network
The network has been set up fine by the cloud provider.
Setting up the firewall
We use iptables
to set up the firewall. A chain is a set of rules. The chain INPUT is predefined for packets that enter the system.
First we set the policy for chain INPUT to target ACCEPT:
<source lang="bash">iptables -P INPUT ACCEPT ip6tables -P INPUT ACCEPT </source> We then flush all the chains:
<source lang="bash">iptables -F ip6tables -F </source> A table in this context is a packet matching table. There are five predefined tables. The default table filter, the tables nat, mangle, raw, and security. We flush the table nat:
<source lang="bash">iptables -t nat -F ip6tables -t nat -F </source> We then delete all the user-defined chains:
<source lang="bash">iptables -X ip6tables -X </source> We then set the policy to drop every packet that enters the system (make sure you are logged in via a video display and not SSH):
<source lang="bash">iptables -P INPUT DROP
ip6tables -P INPUT DROP
</source>
We append to chain INPUT on the loopback interface lo
to jump to target ACCEPT. This is important for applications that use local servers.
<source lang="bash">iptables -A INPUT -i lo -j ACCEPT
ip6tables -A INPUT -i lo -j ACCEPT
</source>
We then append to chain INPUT on interface ens3
a rule to accept connections with states ESTABLISHED and RELATED:
<source lang="bash">iptables -A INPUT -i ens3 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT ip6tables -A INPUT -i ens3 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT </source> We then make sure that incomming TCP connections are SYN packets to prevent SYN flooding:
<source lang="bash">iptables -A INPUT -p tcp ! --syn -m state --state NEW -j DROP ip6tables -A INPUT -p tcp ! --syn -m state --state NEW -j DROP </source> We also drop packets with incoming fragments. This is only for IPv4:
<source lang="bash">iptables -A INPUT -f -j DROP </source> We also drop bogons:
<source lang="bash">iptables -A INPUT -p tcp --tcp-flags ALL ALL -j DROP iptables -A INPUT -p tcp --tcp-flags ALL FIN,PSH,URG -j DROP iptables -A INPUT -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP ip6tables -A INPUT -p tcp --tcp-flags ALL ALL -j DROP ip6tables -A INPUT -p tcp --tcp-flags ALL FIN,PSH,URG -j DROP ip6tables -A INPUT -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP </source> We also reject malformed NULL packets:
<source lang="bash">iptables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP ip6tables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP </source> We can then store the firewall rules with the following commands:
<source lang="bash">iptables-save > /etc/iptables/iptables.rules ip6tables-save > /etc/iptables/ip6tables.rules </source> To make the changes permanent, we can enable and start the Systemd service:
<source lang="bash">systemctl start iptables.service systemctl enable iptables.service systemctl status iptables.service
systemctl start ip6tables.service systemctl enable ip6tables.service systemctl status ip6tables.service </source>
Fail2ban
The service fail2ban is a service for detecting login attempts and blocking it.
We install it with:
<source lang="bash">pacman -S fail2ban
</source>
Using systemd
we can sandbox the fail2ban service with capabilities. To do so, we create a new file /etc/systemd/system/fail2ban.service.d/capabilities.conf
with the following contents:
<source lang="bash">[Service]
PrivateDevices=yes
PrivateTmp=yes
ProtectHome=read-only
ProtectSystem=strict
NoNewPrivileges=yes
ReadWritePaths=-/var/run/fail2ban
ReadWritePaths=-/var/lib/fail2ban
ReadWritePaths=-/var/log/fail2ban
ReadWritePaths=-/var/spool/postfix/maildrop
ReadWritePaths=-/run/xtables.lock
CapabilityBoundingSet=CAP_AUDIT_READ CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_NET_RAW
</source>
Create configuration file /etc/fail2ban/fail2ban.local
with the correct logtarget path:
<source lang="bash">[Definition]
logtarget = /var/log/fail2ban/fail2ban.log
</source>
Create the /var/log/fail2ban/
directory as root.
We have to set up paths for Arch Linux in a jail.local
file:
<source lang="bash">[INCLUDES] before = paths-arch.conf </source>
Yay
With yay, we can keep also the AUR packages up to date.
<source lang="bash">git clone https://aur.archlinux.org/yay.git tar xvzf yay.tar.gz cd yay makepkg -s pacman -U yay*.pkg.tar.xz </source>
SSH access
Configuration
The first service we would like to set up is SSH. We first configure the SSH daemon itself in file /etc/ssh/sshd_config
:
<source lang="bash">PermitRootLogin no TCPKeepAlive no ClientAliveInterval 60 Ciphers aes256-ctr,aes192-ctr,aes128-ctr MACs hmac-sha2-512,hmac-sha2-256 KexAlgorithms diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1 </source> We then restart the daemon:
<source lang="bash">systemctl restart sshd </source>
The firewall
We have to open port 22 for SSH in our firewall:
<source lang="bash">iptables -A INPUT -i ens3 -p tcp --dport 22 -j ACCEPT </source> <source lang="bash">iptables-save > /etc/iptables/iptables.rules ip6tables-save > /etc/iptables/ip6tables.rules </source>
Fail2ban
We add the following jails in /etc/fail2ban/jail.d/
:
In sshd.conf
:
<source lang="bash">[sshd] enabled = true </source>
Installing Nextcloud
The version to install is 19.0.0. We are going to install it on top of nginx
and MariaDB
.
MariaDB
As a first step, we are going to install MariaDB with package mariadb
. Before we start the service, we run:
<source lang="bash">mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql systemctl start mariadb</source> Then add the database (substitute the password with a proper one):
<source lang="sql">CREATE DATABASE nextcloud DEFAULT CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci'; GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextcloud'@'localhost' IDENTIFIED BY 'password'; FLUSH PRIVILEGES;</source>
PHP
Install PHP with package php
. We need to do additional configuration, in /etc/php/php.ini
:
<source lang="bash">date.timezone = Europe/Amsterdam memory_limit = 512M</source> For PHP we need to install the following modules:
module | package | comments |
---|---|---|
ctype | php | |
curl | php | |
dom | php | |
GD | php-gd | enable in php.ini |
iconv | php | enable in php.ini |
json | php | |
libxml | php | |
mbstring | php | |
openssl | php | |
posix | php | |
session | php | |
SimpleXML | php | |
xmlreader | php | |
xmlwriter | php | |
zip | php | |
zlib | php | |
pdomysql | php | enable in php.ini |
mysqli | php | enable in php.ini |
fileinfo | php | |
bz2 | php | enable in php.ini |
intl | php-intl | enable in php.ini |
bcmath | php | enable in php.ini |
gmp | php | enable in php.ini |
imagick | php-imagick | enable in conf.d/imagick.ini |
For further support for imagick
, it is wise to install the following packages:
<source lang="bash">pacman -S ghostscript librsvg libwmf ocl-icd openexr openjpeg2 pango</source>
We enable OPCache in php.ini
:
<source lang="bash">zend_extension=opcache</source> And we set the following options:
<source lang="bash">opcache.enable=1
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.memory_consumption=128
opcache.save_comments=1
opcache.revalidate_freq=1</source>
We enable APCu by installing php-apcu
and enabling it in /etc/php/conf.d/apcu.ini
:
<source lang="bash">extension=apcu.so</source>
Nginx
We install nginx
and set it up to obtain certificates by enabling the service, and open port 80:
<source lang="bash">iptables -A INPUT -i ens3 -p tcp --dport 80 -j ACCEPT
systemctl enable nginx
systemctl start nginx</source>
We get a message that the max_types_hash_size
is not enough. In /etc/nginx/nginx.conf
add in the http
block:
<source lang="bash">types_hash_max_size 4096;</source> We are now ready to install certificates.
Let's Encrypt certificates
Install the following packages to acquire certificates: certbot
, certbot-nginx
.
Then run:
<source lang="bash">certbot --nginx</source> We fill in:
email address | <your email address> |
terms | agree |
share info | no |
domain | cloud.mydomain.net |
It aquires the certificates but cannot find the server block for this domain.
PHP CGI
Install package php-fpm
and in the configuration file /etc/php/php-fpm.d/www.conf
uncomment env[PATH]
. Also set up env[TMP]
in /etc/php/php-fpm.d/www.conf
and make sure the directory is writable for the Nginx user.
We also set the following values in /etc/php/php-fpm.d/www.conf
, taken from the documentation and all values divided by 2.
<source lang="bash">pm.max_children = 60; pm.start_servers = 6; pm.min_spare_servers = 3; pm.max_spare_servers = 9;</source> <source lang="bash">systemctl start php-fpm systemctl enable php-fpm</source>
Nextcloud
<source lang="bash">pacman -S nextcloud</source>
We install the following pacman hook in /etc/pacman.d/hooks/nextcloud.hook
:
<source lang="bash">[Trigger] Operation = Install Operation = Upgrade Type = Package Target = nextcloud Target = nextcloud-app-*
[Action]
Description = Update Nextcloud installation
When = PostTransaction
Exec = /usr/bin/runuser -u http -- /usr/bin/php /usr/share/webapps/nextcloud/occ upgrade</source>
Create a server block in the nginx.conf
:
<source lang="bash">upstream php-handler {
#server 127.0.0.1:9000; server unix:/run/php-fpm/php-fpm.sock;
}
server {
listen 80; listen [::]:80; server_name cloud.mydomain.net; # enforce https return 301 https://$server_name:443$request_uri;
}
server {
listen 443 ssl http2; listen [::]:443 ssl http2; server_name cloud.mydomain.net;
ssl_certificate /etc/letsencrypt/live/cloud.mydomain.net/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/cloud.mydomain.net/privkey.pem;
# 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;" always; add_header Strict-Transport-Security "max-age=15768000; includeSubDomains;" always; # # 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 Referrer-Policy "no-referrer" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Download-Options "noopen" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Permitted-Cross-Domain-Policies "none" always; add_header X-Robots-Tag "none" always; add_header X-XSS-Protection "1; mode=block" always;
# Remove X-Powered-By, which is an information leak fastcgi_hide_header X-Powered-By;
# Path to the root of your installation root /usr/share/webapps/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/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;
# Uncomment if your server is build with the ngx_pagespeed module # This module is currently not supported. #pagespeed off;
location / { rewrite ^ /index.php; }
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\/.+|.+\/richdocumentscode\/proxy)\.php(?:$|\/) { fastcgi_split_path_info ^(.+?\.php)(\/.*|)$; set $path_info $fastcgi_path_info; try_files $fastcgi_script_name =404; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $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;" always; # # 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 Referrer-Policy "no-referrer" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Download-Options "noopen" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Permitted-Cross-Domain-Policies "none" always; add_header X-Robots-Tag "none" always; add_header X-XSS-Protection "1; mode=block" always;
# Optional: Don't log access to assets access_log off; }
location ~ \.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$ { try_files $uri /index.php$request_uri; # Optional: Don't log access to other assets access_log off; }
}</source> We have to create a data directory for Nextcloud:
<source lang="bash">mkdir /var/nextcloud chown http:http /var/nextcloud chmod 750 /var/nextcloud</source> We also have to change permissions for the apps directories:
<source lang="bash">mkdir -p /usr/share/webapps/nextcloud/data
chown -R http:http /usr/share/webapps/nextcloud/{apps,data}
chmod 750 /usr/share/webapps/nextcloud/{apps,data}</source>
We have to give php-fpm
permissions. We do this with the following command:
<source lang="bash">systemctl edit php-fpm.service</source>
This will save the contents in /etc/systemd/system/php-fpm.service.d/override.conf
.
The contents:
<source lang="bash">[Service] ReadWritePaths = /usr/share/webapps/nextcloud/apps ReadWritePaths = /usr/share/webapps/nextcloud/data ReadWritePaths = /etc/webapps/nextcloud/config
- Replace the following path with the Nextcloud data directory
ReadWritePaths = /var/nextcloud</source> We then do:
<source lang="bash">systemctl restart php-fpm</source> We can then open the firewall on port 80 and 443:
<source lang="bash">systemctl stop fail2ban iptables -A INPUT -i ens3 -p tcp --dport 80 -j ACCEPT ip6tables -A INPUT -i ens3 -p tcp --dport 80 -j ACCEPT iptables -A INPUT -i ens3 -p tcp --dport 443 -j ACCEPT ip6tables -A INPUT -i ens3 -p tcp --dport 443 -j ACCEPT iptables-save > /etc/iptables/iptables.rules ip6tables-save > /etc/iptables/ip6tables.rules systemctl start fail2ban</source>
Postinstall
It turns out that the cache was not enabled. We do that by adding the following key to /etc/webapps/nextcloud/config/config.php
:
<source lang="php">'memcache.local' => '\OC\Memcache\APCu',</source> Install the following apps:
- talk
- deck
- notes
Maintenance
The services that are run are:
- mariadb
- nginx
- php-fpm