Installing Nextcloud from EPEL on RHEL or Alma 10
Ah, EL10 time, time to migrate all my servers. I had a Nextcloud server for sharing private content with a band I’ve recently started. It was admittedly a little clumsy to manage, and although there are dozens of guides online for setting it up on RHEL/Rocky/Alma/CentOS 9 using the project’s tarballs, I couldn’t find any regarding using the EPEL package. So, here we are, I’ll be testing this on both Red Hat Enterprise Linux and the free alternative AlmaLinux to see how it goes.
With a fresh install of RHEL or Alma, we need to enable CRB and EPEL.
RHEL:
subscription-manager repos --enable codeready-builder-for-rhel-10-$(arch)-rpms
dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm
Alma:
/usr/bin/crb enable
dnf install epel-release
There are a lot of options for the database or webserver when it comes to Nextcloud. We’re going to use Postgres and Nginx here. It’ll preconfigure some of this for us.
dnf install nginx postgresql-server nextcloud nextcloud-nginx nextcloud-postgresql valkey
Let’s get the postgresql database set up.
postgresql-setup --initdb
systemctl enable --now postgresql
sudo -u postgres psql
Now that we have a SQL prompt, we’ll create the database and user.
postgres=# CREATE USER nextcloud WITH PASSWORD 'thisisnotthebestpassword';
postgres=# CREATE DATABASE nextcloud TEMPLATE template0 ENCODING 'UTF8' OWNER nextcloud;
postgres=# \q
Edit /var/lib/pgsql/data/pg_hba.conf
, change the localhost connections to use md5
instead of ident
.
# "local" is for Unix domain socket connections only
local all all peer
# IPv4 local connections:
host all all 127.0.0.1/32 md5
# IPv6 local connections:
host all all ::1/128 md5
Reload Postgres for changes to take effect.
systemctl reload postgresql
This actually created a default Nginx configuration we’re not going to use. Give it a peek:
root@nextcloud-demo-rhel:/etc/nginx# cat conf.d/nextcloud.conf
upstream php-nextcloud {
server unix:/run/php-fpm/nextcloud.sock;
}
root@nextcloud-demo-rhel:/etc/nginx# cat default.d/nextcloud.conf
rewrite ^/nextcloud$ /nextcloud/ redirect;
location /nextcloud/ {
root /usr/share/;
...
What is useful is that it created a php-fpm config specifically for Nextcloud, taking the guess work out of all the memory and limits changes we would have to make ourselves. The default file is meant to provide the server at /nextcloud/
on top of whatever else you have running. You can go ahead and do that now if you want, I haven’t tested it, but it’d be better if we set up SSL certificates, a domain name, and followed the example config from the project.
I recommend you comment out the default.d
line in nginx.conf
, or null out the default.d/nextcloud.conf
file. The default line here enables PHP execution in the default http server as well, this just isn’t good practice and if you’re not already using this server for anything, it doesn’t add any value.
# Comment out this line in nginx.conf!
#include /etc/nginx/default.d/*.conf;
We’ll need to open the ports and enable nginx.
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
systemctl enable --now nginx
You should have a domain name and a subdomain for it, in my case this is nextcloud-demo.blakecarpenter.dev. If you don’t already know how to get SSL certificates, you can use LetsEncrypt. The quick version is:
dnf install certbot python3-certbot-nginx
certbot run -d nextcloud.your-domain.example --nginx
You’ll probably get an error because you didn’t set up a server name block yet. That’s fine. You have the cert and we’ll make a block shortly.
I’ll use LetsEncrypt paths for the example config.
Okay, let’s edit /etc/nginx/conf.d/nextcloud.conf
. We’re going to leave the configured php-fpm upstream at the top and make changes on our way down. This is based on the provided Nginx config from the Nextcloud project, with some changes. Most of the comments in this config are from their documentation.
upstream php-nextcloud {
server unix:/run/php-fpm/nextcloud.sock;
}
server {
listen 80;
listen [::]:80;
server_name nextcloud-demo.blakecarpenter.dev; # change this to your domain name
# Prevent nginx HTTP Server Detection
server_tokens off;
# Enforce HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name nextcloud-demo.blakecarpenter.dev; # change this to your domain name
root /usr/share/nextcloud/;
# Change these to your certificate paths.
ssl_certificate /etc/letsencrypt/live/nextcloud-demo.blakecarpenter.dev/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/nextcloud-demo.blakecarpenter.dev/privkey.pem;
access_log /var/log/nginx/nextcloud.access.log;
error_log /var/log/nginx/nextcloud.error.log;
# Prevent nginx HTTP Server Detection
server_tokens off;
# HSTS settings
# 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 Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" always;
# set max upload size and increase upload timeout:
client_max_body_size 512M;
client_body_timeout 300s;
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 text/javascript application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm 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;
# Pagespeed is not supported by Nextcloud, so if your server is built
# with the `ngx_pagespeed` module, uncomment this line to disable it.
#pagespeed off;
# The settings allows you to optimize the HTTP2 bandwidth.
# See https://blog.cloudflare.com/delivering-http-2-upload-speed-improvements/
# for tuning hints
client_body_buffer_size 512k;
# HTTP response headers borrowed from Nextcloud `.htaccess`
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header X-Robots-Tag "noindex, nofollow" 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;
# Set .mjs and .wasm MIME types
# Either include it in the default mime.types list
# and include that list explicitly or add the file extension
# only for Nextcloud like below:
include mime.types;
types {
text/javascript mjs;
}
# Specify how to handle directories -- specifying `/index.php$request_uri`
# here as the fallback means that Nginx always exhibits the desired behaviour
# when a client requests a path that corresponds to a directory that exists
# on the server. In particular, if that directory contains an index.php file,
# that file is correctly served; if it doesn't, then the request is passed to
# the front-end controller. This consistent behaviour means that we don't need
# to specify custom rules for certain paths (e.g. images and other assets,
# `/updater`, `/ocs-provider`), and thus
# `try_files $uri $uri/ /index.php$request_uri`
# always provides the desired behaviour.
index index.php index.html /index.php$request_uri;
# Rule borrowed from `.htaccess` to handle Microsoft DAV clients
location = / {
if ( $http_user_agent ~ ^DavClnt ) {
return 302 /remote.php/webdav/$is_args$args;
}
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
# Make a regex exception for `/.well-known` so that clients can still
# access it despite the existence of the regex rule
# `location ~ /(\.|autotest|...)` which would otherwise handle requests
# for `/.well-known`.
location ^~ /.well-known {
# The rules in this block are an adaptation of the rules
# in `.htaccess` that concern `/.well-known`.
location = /.well-known/carddav { return 301 /remote.php/dav/; }
location = /.well-known/caldav { return 301 /remote.php/dav/; }
location /.well-known/acme-challenge { try_files $uri $uri/ =404; }
location /.well-known/pki-validation { try_files $uri $uri/ =404; }
# Let Nextcloud's API for `/.well-known` URIs handle all other
# requests by passing them to the front-end controller.
return 301 /index.php$request_uri;
}
# Rules borrowed from `.htaccess` to hide certain paths from clients
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; }
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; }
# This section is from the EPEL provided config to track the system
# installed versions of the apps.
location ~ ^/apps-appstore/(.*)$ {
alias /var/lib/nextcloud/apps/$1;
}
location ~ ^/assets/(.*)$ {
alias /var/lib/nextcloud/assets/$1;
}
# Ensure this block, which passes PHP files to the PHP process, is above the blocks
# which handle static assets (as seen below). If this block is not declared first,
# then Nginx will encounter an infinite rewriting loop when it prepends `/index.php`
# to the URI, resulting in a HTTP 500 error response.
location ~ \.php(?:$|/) {
# Required for legacy support
rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode(_arm64)?\/proxy) /index.php$request_uri;
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;
fastcgi_param modHeadersAvailable true; # Avoid sending the security headers twice
fastcgi_param front_controller_active true; # Enable pretty urls
fastcgi_pass php-nextcloud;
fastcgi_intercept_errors on;
fastcgi_request_buffering on; # Required as PHP-FPM does not support chunked transfer encoding and requires a valid ContentLength header.
fastcgi_max_temp_file_size 0;
}
# Serve static files
location ~ \.(?:css|js|mjs|svg|gif|ico|jpg|png|webp|wasm|tflite|map|ogg|flac)$ {
try_files $uri /index.php$request_uri;
# HTTP response headers borrowed from Nextcloud `.htaccess`
#add_header Cache-Control "public, max-age=15778463$asset_immutable";
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header X-Robots-Tag "noindex, nofollow" always;
add_header X-XSS-Protection "1; mode=block" always;
access_log off; # Optional: Don't log access to assets
}
location ~ \.(otf|woff2?)$ {
try_files $uri /index.php$request_uri;
expires 7d; # Cache-Control policy borrowed from `.htaccess`
access_log off; # Optional: Don't log access to assets
}
# Rule borrowed from `.htaccess`
location /remote {
return 301 /remote.php$request_uri;
}
location / {
try_files $uri $uri/ /index.php$request_uri;
}
}
If your configuration was correct, the following should pass:
nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
If you have that SELinux hassle enabled, also do this:
setsebool -P httpd_can_network_connect on
Although you’ll probably move faster than bots, it might be a good idea right now to firewall your server off to only your IP, then remove the block after you create your user account. Consult the documentation of your cloud provider, your hardware, or just use firewalld.
Restart the web services.
systemctl restart php-fpm nginx
Navigate to your new site and you should see a setup form. Create your user account, fill in the database details and click Install.
You’ll come to the next screen asking you if you want to install certain apps. Make your selections.
Let’s do some tuning. Click your initial in the upper right and go to “Administration Settings.” You’ll see some warnings here. Let’s address the first ones you probably see.
As root, change into /usr/share/nextcloud
and find the occ
command. You probably have “mimetype migrations” and “Detected some missing optional indices.” warnings, let’s address that now.
./occ maintenance:repair --include-expensive
./occ db:add-missing-indices
You might get warnings about PHP memory limits, but this is because you’re running it with the defaults, whereas php-fpm in nginx is configured to use the Nextcloud config. This is benign, don’t worry about it.
We do want to edit the opcache setting in /etc/php.d/10-opcache.ini
though. Uncomment opcache.interned_strings_buffer
and set the value to something higher than 8, I’m using 12.
We can leverage Valkey for caching which can help improve performance. Edit /etc/nextcloud/config.php
and add the following settings to the array:
'memcache.local' => '\OC\Memcache\APCu',
'memcache.distributed' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' => [
'host' => 'localhost',
'port' => 6379,
],
Enable Valkey and restart php-fpm again:
systemctl enable --now valkey
systemctl restart php-fpm
If you reload the page now, it should be a little cleaner. Most of what remains here is safe to ignore, but you can continue configuring it if you wish.
Click “Personal Info” on the left and fill in your email address. You won’t be able to configure email if it doesn’t have a place to send a test message. Then, under Basic Settings you can fill in SMTP settings, which I recommend if you have an account for this sort of thing.
Also under Basic Settings, you’ll see a panel called “Background Jobs”, switch this to “Cron (Recommended)”, and then set up a system cron to run the PHP script every 5 minutes.
crontab -u apache -e
# Add the following and save
*/5 * * * * php -f /usr/share/nextcloud/cron.php
# Make sure it ends with a new line.
Upload some content, browse around, make sure everything is working as expected.
At this point, you should have a working Nextcloud server. You’ll be able to update it just by doing dnf update
rather than having the portal fetch tarballs for you. It won’t be as up to date, but there is a convenience factor.