Nextcloud with ModSecurity v. 3 Web Application Firewall

Nextcloud with Web Application Firewall (nginx)

Last update Aug., 03rd 2019:
“SecRuleRemoveById 200002 911100 949110 ” was added to work with Nextcloud contacts

We assume nginx is already installed as described here. Please prepare your webserver for ModSecurity v. 3.0.3 and OWASP CRS v. 3.1.1 by installing the following packages

sudo -s
apt install apt-utils autoconf automake build-essential git libcurl4-openssl-dev libgeoip-dev liblmdb-dev libpcre++-dev libtool libxml2-dev libyajl-dev pkgconf wget zlib1g-dev -y

Download and compile the ModSecurity 3.0 sources

cd /usr/local/src
git clone --depth 1 -b v3/master --single-branch

Change into the ModSecurity directory and compile the module

cd ModSecurity
git submodule init
git submodule update

The compilation takes about 15 minutes, depending on the processing power of your system.

Note: It’s safe to ignore messages like

fatal: No names found, cannot describe anything.

make install

Now download the NGINX connector for ModSecurity and compile it as a dynamic module

git clone --depth 1

Determine which version of NGINX is running

nginx -v


nginx -V

and download the corresponding sources e.g. version 1.17.2

tar zxvf nginx-1.17.2.tar.gz

Compile and copy the dynamic module

cd nginx-1.17.2/
./configure --with-compat --add-dynamic-module=../ModSecurity-nginx
make modules
cp objs/ /etc/nginx/modules

Load the NGINX ModSecurity connector dynamic module by adding (and verify) the red row(s)

nano /etc/nginx/nginx.conf
load_module modules/;
user www-data;
worker_processes auto;
pid /var/run/;
events {
worker_connections 1024;
multi_accept on; use epoll;
http {
server_names_hash_bucket_size 64;
upstream php-handler {
server unix:/run/php/php7.3-fpm.sock;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
# please customize the ip-range properly
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;
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 valid=30s;
#resolver valid=30s; *is recommended but requires a valid DNS configuration*
resolver_timeout 5s;
include /etc/nginx/conf.d/*.conf;

Configure, enable and test ModSecurity issuing a simple command

mkdir /etc/nginx/modsec
wget -P /etc/nginx/modsec/
mv /etc/nginx/modsec/modsecurity.conf-recommended /etc/nginx/modsec/modsecurity.conf

Change the SecRuleEngine directive in the configuration to change from the default “detection only” mode to actively dropping malicious traffic and create a test rule

sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/nginx/modsec/modsecurity.conf
nano /etc/nginx/modsec/main.conf
# Edit to set SecRuleEngine On
Include "/etc/nginx/modsec/modsecurity.conf"
# Basic test rule
SecRule ARGS:testparam "@contains test" "id:1234,deny,status:403"

Add the modsecurity and modsecurity_rules_file directives to the NGINX configuration (e.g. nextcloud.conf) to enable ModSecurity

server {
listen 80 default_server;
listen [::]:80 default_server;
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
location ^~ /.well-known/acme-challenge {
proxy_set_header Host $host;
location / {
return 301 https://$host$request_uri;
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
root /var/www/nextcloud/;
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;
#SOCIAL app enabled? Please uncomment the following row
#rewrite ^/.well-known/webfinger /public.php?service=webfinger last;
#WEBFINGER app enabled? Please uncomment the following two rows.
#rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
#rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
client_max_body_size 10240M;
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 ~ \.(?:flv|mp4|mov|m4a)$ {
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\/.+|oc[ms]-provider\/.+).php(?:$|\/) {
fastcgi_split_path_info ^(.+?.php)(\/.*|)$;
include fastcgi_params;
include php_optimization.conf;
fastcgi_pass php-handler;
fastcgi_param HTTPS on;
location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) {
try_files $uri/ =404;
index index.php;
location ~ .(?:css|js|woff2?|svg|gif|map|png|html|ttf|ico|jpg|jpeg)$ {
try_files $uri /index.php$request_uri;
access_log off;
expires 360d;
cp /usr/local/src/ModSecurity/unicode.mapping /etc/nginx/modsec/
service nginx restart

Issue the following curl command and the status html code 403 confirms that the rule is working properly.

curl http://localhost?testparam=test

You will find ModSecurity messages in the nginx error.log issuing

tail -f /var/log/nginx/error.log | grep "id\ \"1234\""

Enable the OWASP CRS and restart nginx

cd /usr/local/src
tar -xzvf v3.1.1.tar.gz
cd owasp-modsecurity-crs-3.1.1
cp crs-setup.conf.example crs-setup.conf

Add Include directives in the main NGINX WAF configuration file /etc/nginx/modsec/main.conf to read in the CRS configuration and rules. Comment out any other rules that might already exist in the file, such as the sample SecRule directive created in that guide.

nano /etc/nginx/modsec/main.conf 
# Edit to set SecRuleEngine On
Include "/etc/nginx/modsec/modsecurity.conf"
# Basic test rule
# SecRule ARGS:testparam "@contains test" "id:1234,deny,status:403"
# OWASP CRS v3 rules
Include /usr/local/src/owasp-modsecurity-crs-3.1.1/crs-setup.conf
Include /usr/local/src/owasp-modsecurity-crs-3.1.1/rules/*.conf
service nginx restart

If you’ll call the testpage again, the 403 page is gone

Prevent NGINX from being updated using apt upgrade:

apt-mark hold nginx

The main log in ModSecurity is the audit log, which logs all attacks, including potential attacks, that occur. Call the following URL in your browser 

and verify this request was as well blocked with http code 403 in your browser as logged in the audit log

tail -f /var/log/modsec_audit.log | grep aphpfilethatdoesnotexist
nano /etc/nginx/modsec/modsecurity.conf

Change SecRequestBodyLimit 13107200 to e.g. 10GB according to your upload limits in PHP/Nextcloud

#SecRequestBodyLimit 13107200
SecRequestBodyLimit 10000000000

If Nextcloud behaves bizarrely use this audit.log to identify ModSecurity as the root of the issue or to investigate further as examplarily shown:


cat /var/log/modsec_audit.log | egrep -i "warning\." | egrep -i "id \"[0-9]{6}\"" -o | sort | uniq -c | sort -nr
7 id "980130"
7 id "930110"
7 id "930100"


cat /var/log/modsec_audit.log | egrep -i "access denied" | egrep -i "id \"[0-9]{6}\"" -o | sort | uniq -c | sort -nr
7 id "949110"

More details regarding the audit.log can be found here.

Finally enhance ModSecurity’s main.conf for Nextcloud

nano /etc/nginx/modsec/main.conf
# Edit to set SecRuleEngine On
Include "/etc/nginx/modsec/modsecurity.conf"
# Basic test rule
# SecRule ARGS:testparam "@contains test" "id:1234,deny,status:403"
# OWASP CRS v3 rules
Include /usr/local/src/owasp-modsecurity-crs-3.1.1/crs-setup.conf
Include /usr/local/src/owasp-modsecurity-crs-3.1.1/rules/*.conf
#Exceptions for Nextcloud
SecRuleRemoveById 200002 911100 949110

and restart NGINX

service nginx restart

Enjoy your hardened Nextcloud server!

Sources (Aug,1st 2019):

Don’t forget to backup your Nextcloud

Find more instructions here: Nextcloud backup and restore

Carsten Rieger

Carsten Rieger is a senior system engineer in full-time and also working as an IT freelancer. He is working with linux environments for more than 15 years, an Open Source enthusiast and highly motivated on linux installation and troubleshooting. Mostly working with Debian/Ubuntu Linux, Nginx and Apache web server, MariaDB/MySQL/PostgreSQL, PHP, Cloud infrastructure (e.g. Nextcloud) and other open source projects (e.g. Roundcube) and in voluntary work for the Dr. Michael & Angela Jacobi Stiftung for more than 7 years.