Achieve security excellence without breaking the budget!

Download guide

Install and secure your NextCloud server with CrowdSec

In this tutorial, we will cover installing and securing a Nextcloud instance with the CrowdSec software.

Nextcloud is an extensible collaborative drive tool to replace traditional office suites and drives. (GSuite and Microsoft 365). The focus is on privacy with an easily self-hosting tool.

CrowdSec is a collaborative security solution based on the principle of analysis and the correlation of application logs. All servers running CrowdSec make it possible to collect intruder signals, block them and report them to the rest of the community. This means that all users benefit from quality protection (from the crowd) and all this for free.

Architecture

Here is the architecture of our Nextcloud stack.

The CrowdSec container is mounted to the logs from the reverse proxy as well as to the Nextcloud logs. This gives it the ability to detect attacks.

The purpose of the Openresty container is to route user requests to the Nextcloud container. It interacts with CrowdSec via the local API and is able to mitigate threats from malicious requests by blocking them or forcing them through a captcha challenge.

The certbot container allows one to obtain certificates for free with the let’s encrypt service. It also manages the system of challenges allowing them to be obtained.

Finally, the Nextcloud and Database containers ensure the operation of the Nextcloud service.

By going through the docker-compose.yml file, you will notice two more containers (Redis, Cron). Redis is an in-memory database used by Nextcloud to quickly store information. Cron is a tool to automate maintenance tasks.

Volumes are also mounted to allow data exchange between containers through the file system.

Prerequisites

You will need a reCAPTCHA V2 API key to request on the Google website as well as a domain name already pointing to your VPS.

For this example we will use a virtual machine running Debian 11 with the following configuration:

  • 2 vCPUs
  • 2 GB RAM
  • 100 GB of disk space (or whatever your Nextcloud instance requires)

Once our server is freshly installed, we connect to it via SSH using the root account.

User creation

First of all, we create the user bob.

# Creating the useradduser bob --disabled-password
passwd bob

# Installing sudo
apt-get -y install sudo

We then provide suitable privileges:

nano /etc/sudoers

Now add this line:

# User privilege specification
bob     ALL=(ALL:ALL) ALL

And finally, we harden the security of the SSH server by modifying the configuration

# Edit sshd config file
sudo nano /etc/ssh/sshd_config

# Force ssh v2
Protocol 2
# Disable root login
PermitRootLogin  no
# Enable ssh logs
SyslogFacility AUTH
LogLevel INFO
# Limit maximum failed authentifications
MaxAuthTries 5
# Limit maximum concurrent sessions
MaxSessions 2
# Disable empty passwords
PermitEmptyPasswords no

sudo systemctl restart sshd.service

Update

We then update the VM:

sudo apt-get update
sudo apt-get -y dist-upgrade

Next, we set up automation to install security updates using the unattended-upgrade package.

sudo apt-get install -y apt-listchanges unattended-upgrades

Firewall

To protect against external threats, it is important to close all unnecessary ports on our server. For that we install iptables and uses ufw to install it.

sudo apt-get install -y iptables iptables-persistent ufw

Once installed, we open the following ports:

  • 22 to allow SSH connections
  • 80 to allow HTTP connections
  • 443 to allow HTTPS connections

Opening the ports:

sudo ufw allow ssh http https

When the configuration is done we enable the firewall:

sudo ufw enable

Redis

Redis requires just a small modification to the Linux kernel:

sudo sysctl -w vm.overcommit_memory=1
sudo sysctl -p /etc/sysctl.conf

Docker installation

With our OS now being up to date, we install docker and docker-compose to be able to manage the containers that will run the Nextcloud stack.

# Installing docker-ce
sudo apt-get install -y ca-certificates curl gnupg lsb-release
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo 
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian 
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io

# Installation of docker-compose
mkdir -p ~/.docker/cli-plugins/
curl -SL https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
chmod +x ~/.docker/cli-plugins/docker-compose

Starting up the stack

Before cloning the example repository, we install git.

sudo apt install -y git

We clone the repository containing the configuration files.

git clone https://github.com/Sykursen/crowdsec-nextcloud.git

We must now edit the file so that they work with our configuration. We are replacing all example.org fields with our domain, as well as adding strong passwords to the .env file.

Once all the files have been created and edited, we deploy our stack.

sudo docker compose up -d

Adding bouncer

Once the stack is successfully started, we’ll generate a token for our bouncer so we can add it to CrowdSec.

sudo docker exec nextcloud-crowdsec-1 cscli bouncers add openresty-nextcloud

Add the token to the file /crowdsec/crowdsec-openresty-bouncer.conf.yaml

API_KEY=

Restart the bouncer

sudo docker compose restart openresty

HTTPS Support

For more security, it is recommended to switch to the HTTPS protocol by adding a domain name and a TLS certificate.

Once our stack is started, we can request a certificate with the following command.

Make sure to replace example.com with your domain name

sudo docker compose run --rm  certbot certonly --webroot --webroot-path /var/www/certbot/ -d example.com

Once the certificate has been obtained, we edit the .conf/ file by adding these lines at the end of the file.

Again, make sure to replace example.com with your domain name

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_tokens off;
  proxy_hide_header X-Powered-By;

  client_max_body_size 512M;
  client_body_timeout 300s;
  fastcgi_buffers 64 4K;
  
  ssl_certificate /etc/nginx/ssl/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/nginx/ssl/live/example.com/privkey.pem;
  
  access_log /var/log/nginx/access.log;
  
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_prefer_server_ciphers off;
  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_session_timeout 1d;
  ssl_session_cache shared:MozSSL:10m;
  ssl_session_tickets off;

  ssl_stapling on;
  ssl_stapling_verify on;

  add_header Strict-Transport-Security "max-age=15552000" always;

  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/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;

  location /.well-known/carddav {
    return 301 $scheme://example.com/remote.php/dav;
  }

  location /.well-known/caldav {
      return 301 $scheme://example.com/remote.php/dav;
  }
  
	location / {
		proxy_pass http://app;
		proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
  }
}

We restart our reverse-proxy one last time:

sudo docker compose restart openresty

Testing

Manual blocking

You can test the blocking capabilities of CrowdSec by manually adding a ban or captcha rule.

sudo docker exec -it nextcloud-crowdsec-1 /bin/bash
cscli decisions add --ip  --duration 1m --type 
exit

Result of blocking by captcha

Result of blocking by ban

Automatic detection

For automatic detection, we will enumerate 5 different users. From the 5th attempt, our enumeration attempt is detected and blocked by an immediate blocking decision.

Conclusion

In this configuration, our Nextcloud instance is exposed to the internet. It is protected by CrowdSec and blocks intrusion attempts automatically.

Let’s Encrypt SSL certificates expire every 3 months. Here is the procedure to regenerate them.

sudo docker compose run --rm certbot renew

Sources

These resources permitted me to write this article.

You may also like

Protect Your Applications with AWS WAF and CrowdSec: Part I
Tutorial

Protect Your Applications with AWS WAF and CrowdSec: Part I

Learn how to configure the AWS WAF Remediation Component to protect applications running behind an ALB that can block both IPs and countries.

Protect Your Serverless Applications with AWS WAF and CrowdSec: Part II
Tutorial

Protect Your Serverless Applications with AWS WAF and CrowdSec: Part II

Learn how to protect your serverless applications hosted behind CloudFront or Application Load Balancer with CrowdSec and the AWS WAF.

Securing A Multi-Server CrowdSec Security Engine Installation With HTTPS
Tutorial

Securing A Multi-Server CrowdSec Security Engine Installation With HTTPS

In part II of this series, you learn about the three different ways to achieve secure TLS communications between your CrowdSec Security Engines in a multi-server setup.