Written on 27. December 2022

NGINX as reverse proxy with ModSecurity and OWASP Ruleset on Debian

I have divided my home network into three separate areas: a datacenter, a DMZ (demilitarized zone), and an internal client network. To prevent my clients from having direct access to the datacenter, I have set up an NGINX reverse proxy in the DMZ. This allows both clients within the network and those from the WAN (wide area network) to connect. It is great to be able to secure all sites with a Let’s Encrypt certificate.

In the coming days, I plan to publish an article on configuring Apache2 as a reverse proxy with ModSecurity and the OWASP ruleset.


To install NGINX and Certbot from the default repository on a debian-based operating system, you can use the package manager apt:

apt update 
apt install nginx certbot python3-certbot-nginx

Certbot wildcard certificate request

To request a wildcard certificate for your domain using a DNS challenge and Certbot, you will need to follow these steps:

certbot certonly --manual --preferred-challenges=dns --server https://acme-v02.api.letsencrypt.org/directory -d *.stangneth.com

Follow the instructions provided by Certbot for creating a TXT record in your DNS settings. This will typically involve logging into your domain provider’s control panel and adding a new TXT record with the value provided by Certbot.

Once the TXT record has been added, return to the terminal and press Enter to continue the certificate request process.

Certbot will verify that the TXT record has been added correctly and, if successful, will issue a wildcard certificate for your domain.

Please note that this process may vary depending on your specific domain provider and the DNS management tools they offer. You may need to consult your provider’s documentation or support resources for additional guidance.

It will be stored under:


Configure NGINX

Start and enable NGINX:

systemctl start nginx
systemctl enable nginx

Check if NGINX is running without failures:

systemctl status nginx

To unlink the default site in NGINX, you will need to remove the symbolic link to the default site configuration file from the sites-enabled directory. This directory is used by NGINX to determine which site configurations should be enabled and used when the server starts. Disable the default site:

unlink /etc/nginx/sites-enabled/default

Switch to the sites-available directory and create an own configuration file:

cd /etc/nginx/sites-enabled/
vi 001-example.stangneth.com.conf

To catch all default requests on port 80 and port 443 (HTTP and HTTPS) and return a 404 error in NGINX, you can use the following configuration:

server {
	listen 80 default_server;
	server_name _;
	return 404;
server {
	listen 443 ssl default_server;
	server_name _;
	ssl_certificate /etc/letsencrypt/live/stangneth.com/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/stangneth.com/privkey.pem;
	return 404;

This configuration sets up two server blocks, one for port 80 and one for port 443. The default_server directive specifies that these blocks should catch all default requests that do not match any other server block. The return directive is used to return a 404 error to the client.

To rewrite all HTTP requests to HTTPS in NGINX, you can use the following configuration:

server {
	listen 80;

	server_name example.stangneth.com;
	rewrite ^ https://$host$request_uri permanent;

Finally you can add the part for the first website on port 443:

server {
	listen 443 ssl;

	server_name example.stangneth.com;                              
	ssl_certificate /etc/letsencrypt/live/stangneth.com/fullchain.pem;       
	ssl_certificate_key /etc/letsencrypt/live/stangneth.com/privkey.pem;     
	ssl_session_cache builtin:1000 shared:SSL:10m;                        
	ssl_protocols TLSv1.1 TLSv1.2;                                  
	ssl_prefer_server_ciphers on;                                         
	access_log /var/log/nginx/access.log;                                 

	location / {

		proxy_read_timeout 90;

I added an allow for a specific IP range, so not everyone from the Internet can reach the page. That can be good for ressources which should have an offical certificate from letsencrypt but not reachable from the Internet. In my case a mediawiki.

Activate the web site:

ln -s /etc/nginx/sites-avaialable/001-example.stangneth.com.conf /etc/nginx/sites-enabled/001-example.stangneth.com.conf

Restart now NGINX and check if the site is reachable (be sure DNS is ok!):

service nginx configtest
systemctl restart nginx
systemctl status nginx

Install ModSecurity

Install dependecies:

apt install -y 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

Switch to /usr/local/src and clone the git repo:

cd /usr/local/src
git clone --depth 1 -b v3/master --single-branch https://github.com/SpiderLabs/ModSecurity

Switch to the directory and install ModSecurity:

cd ModSecurity/
git submodule init
git submodule update
make install

Next clone the NGINX modsecurity connector from git:

cd ..
git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git

Check which version of NGINX is installed:

nginx -v

In this case it’s 1.18.0. So I downloaded the source code:

wget http://nginx.org/download/nginx-1.18.0.tar.gz
tar zxvf nginx-1.18.0.tar.gz

Now compile the modsecurity-nginx module and copy it to the module directory:

cd nginx-1.18.0/
./configure --with-compat --add-dynamic-module=../ModSecurity-nginx
make modules
cp /usr/local/src/nginx-1.18.0/objs/ngx_http_modsecurity_module.so /etc/nginx/modules/

Within the nginx.conf file load the module:

vi /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;

Restart NGINX and check the status:

systemctl restart nginx
systemctl status nginx

Configure modsecurity

Create a new directory for the ModSecurity configuration files:

mkdir /etc/nginx/modsec

Download the default modsecurity.conf-recommed from the SpiderLabs git repo:

wget -P /etc/nginx/modsec/ https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/modsecurity.conf-recommended
mv /etc/nginx/modsec/modsecurity.conf-recommended /etc/nginx/modsec/modsecurity.conf

To be sure that ModSecurity can find the unicode.mapping file copy it to /etc/nginx/modsec

cp /usr/local/src/ModSecurity/unicode.mapping /etc/nginx/modsec/

In the modsecurity.conf enable the SecRuleEngine from “DetectionOnly” to “On”:

vi /etc/nginx/modsec/modsecurity.conf

Create a .conf file where it’s possible to load necessary configurations for specific websites:

vi /etc/nginx/modsec/main.conf
Include "/etc/nginx/modsec/modsecurity.conf"

OWASP Ruleset

Download the latest OWASP coreruleset from git repo:

cd /usr/local/src
wget https://github.com/SpiderLabs/owasp-modsecurity-crs/archive/v3.0.2.tar.gz
tar -xzvf v3.0.2.tar.gz
cd owasp-modsecurity-crs-3.0.2/

Copy the example configuration file:

cp crs-setup.conf.example crs-setup.conf

Open the main.conf file and load the ruleset:

# OWASP CRS v3 rules
Include /usr/local/src/owasp-modsecurity-crs-3.0.2/crs-setup.conf
Include /usr/local/src/owasp-modsecurity-crs-3.0.2/rules/*.conf

Enable ModSecurity for specific website

To enable ModSecurity for a specific site edit the .conf file.

vi /etc/nginx/sites-available/001-example.stangneth.com.conf
server {
	listen 443 ssl;

	server_name example.stangneth.com;                              

	modsecurity on;
	modsecurity_rules_file /etc/nginx/modsec/main.conf;

	modsecurity_rules '
            SecRuleRemoveById 949110

The SecRuleRemoveById section is an example. It will often happen that a website dont need the complete ruleset or false/positive alerts will be triggerd.

It is possible to check which rule was taken:

tail -f /var/log/nginx/error.log

An output can look like this example:

2022/12/27 14:38:39 [error] 1155#1155: *18 [client] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:ANOMALY_SCORE' (Value: `5' ) [file "/usr/local/src/owasp-modsecurity-crs-3.0.2/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "44"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [data ""] [severity "2"] [ver ""] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname ""] [uri "/api/v1/tickets/14"] [unique_id "167188911882.664343"] [ref ""], client:, server: example.stangneth.com, request: "PUT /api/v1/tickets/14?all=true HTTP/1.1", host: "example.stangneth.com", referrer: "https://example.stangneth.com/"

So it was neccessary to remove the [id “949110”] from the configuration for THIS website.

2 Comments on NGINX as reverse proxy with ModSecurity and OWASP Ruleset on Debian

    • Thank you so much for your kind words! I’m delighted to hear that the guide was helpful for you. If you have any further questions or need any assistance, please don’t hesitate to reach out to me. I’m here to help. Thanks again and all the best!

Leave a Reply

Your email address will not be published. Required fields are marked *