🎁 End of Year Gift: Use Code CROWDMAS25 for 60% Off CrowdSec Console Premium.

Check out now
ScaleCommerce x CrowdSec

Multi-Tenant WAF In The Real World: How ScaleCommerce Uses CrowdSec

While running workshops with ScaleCommerce in Berlin, we worked on a use case that many SecOps teams will recognise.

ScaleCommerce wanted a single pool of CrowdSec WAF instances to protect all of its customers. The raw performance and scaling aspects are relatively straightforward and well-documented. The real challenge was implementing proper multi-tenancy in the WAF configuration, especially the ability for customers to disable specific rules or sets of rules without disrupting operations.

In their case, this means hundreds of customers, thousands of websites, and a long list of custom rules tied to business logic. ScaleCommerce focuses on e-commerce, so it has a significant number of rules regarding scalping, bot protection, and similar behaviours.

From a SecOps point of view, the question was simple: how do you maintain a single shared CrowdSec WAF layer, provide each tenant with the necessary flexibility, avoid constant reloads, and still keep the entire system manageable?

The workshop led to two approaches.

Take 1: Configuration-based multi tenancy

The initial idea was to implement per-customer configuration directly in CrowdSec using hooks.

Here is a simplified example of what that looked like:

name: custom/disable-rules-per-customer
pre_eval:
 - filter: |
     let custom_scenarios = {
      "some-shop.com": ["crowdsecurity/generic-wordpress-uploads-listing", "crowdsecurity/generic-wordpress-uploads-listing"],
      "another-shop.com": ["crowdsecurity/generic-wordpress-uploads-listing"],
    };
    map(custom_scenarios[req.Header.Get(“Host”)],
        {
          RemoveInBandRuleByName(#)
        })

This snippet uses pre_eval hooks to customize, in real-time, the list of enabled or disabled rules for a given fully qualified domain name.

In practice, it does the following:

  • It maps the requested domain name, for example, some-shop.com, to a list of rules that need to be disabled for that specific site.
  • For some-shop.com the rules crowdsecurity/generic-wordpress-uploads-listing and crowdsecurity/generic-wordpress-uploads-listing are disabled, while for another-shop.com only crowdsecurity/generic-wordpress-uploads-listing is disabled.

Under the hood, this relies on the expression language embedded in CrowdSec, along with the RemoveInBandRuleByName helper that disables the required rule or rules.

Even in this simple form, it reveals an important aspect for SecOps: CrowdSec can reconfigure itself at runtime based on the characteristics of HTTP requests. You are not limited to static YAML. You can shape WAF behaviour using logic and context.

However, this configuration-based method has two clear drawbacks once you reach the ScaleCommerce size:

  • As the number of distinct configurations increases, the map becomes more difficult to read and reason about.
  • Each configuration change requires editing code or a YAML file and then reloading CrowdSec, which makes the process cumbersome and risky at scale.

For a handful of tenants, this is fine. For hundreds, it becomes a maintenance and change management problem.

Take 2: A dynamic approach that treats config as data

Given ScaleCommerce’s context, reloading CrowdSec on each configuration change was not acceptable. Reloads are smooth and harmless at the Nginx level, but not at the CrowdSec level. To avoid this, we decided that the best way forward was to ship the configuration directly in the data processed by CrowdSec, inside the HTTP request itself.

This aligns well with ScaleCommerce, as they already utilize custom Lua code at the ingress (Nginx or OpenResty) level to modify incoming requests based on each customer’s configuration and customization.

The idea is simple:

  1. At the edge, Nginx looks at the request, applies tenant-specific logic, and decides which rules should be disabled.
  2. It writes this decision into a header, for example, X-Custom-Disabled-Waf-Rules.
  3. CrowdSec reads this header via pre_eval and disables the listed rules for that specific request, both in-band and out of band.

Here is the CrowdSec scenario used in that approach (simplified):

name: smoxy/appsec-config
pre_eval:
 - filter: IsInBand == true and req.Header.Get("X-Custom-Disabled-Waf-Rules") != ""
   apply:
     - |
       let rules = split(req.Header.Get("X-Custom-Disabled-Waf-Rules"), ",");
      map(
        rules,
        {RemoveInBandRuleByName(#)}
      )
 - filter: IsOutBand == true and req.Header.Get("X-Custom-Disabled-Waf-Rules") != ""
   apply:
     - |
       let rules = split(req.Header.Get("X-Custom-Disabled-Waf-Rules"), ",");
      map(
        rules,
        {RemoveOutBandRuleByName(#)}
      )             


on_match:
 - filter: IsOutBand == true
   apply:
     - SendAlert()

This snippet again uses pre_eval hooks to customize, in real-time, which rules are enabled or disabled for a given request.

The difference is that the source of truth is no longer a large static map. Instead, the scenario reads the incoming X-Custom-Disabled-Waf-Rules header as the list of rules to disable. The upstream (nginx or any other component) can set this header as needed, depending on which tenant or environment is targeted.

On the Nginx side, it might look like this:

server {
 listen 80 default_server;
 listen [::]:80 default_server;
 root /var/www/html;

 rewrite_by_lua_block {
   ngx.req.clear_header("X-Custom-Disabled-Waf-Rules")
  
   if ngx.var.uri:find("^/something/") then
    ngx.req.set_header("X-Custom-Disabled-Waf-Rules", "crowdsecurity/vpatch-env-access")
   end
 }

In a production environment, the Lua code would not hardcode values. It would load data from an external configuration file, database, or a dedicated configuration service. The key idea remains the same: you inject headers that CrowdSec uses at runtime to reconfigure the WAF.

What this means for SecOps

From a SecOps perspective, this pattern has several clear benefits:

  • You keep a single, shared CrowdSec WAF layer for all tenants, which simplifies operations and monitoring.
  • Tenant-specific behaviour is controlled upstream, in your own configuration systems and code, instead of being buried in long YAML files.
  • You avoid frequent CrowdSec reloads to handle “just one more exception” for a particular customer. Changes flow through as data in the request.
  • You still benefit from CrowdSec’s detection capabilities and helper functions, including the ability to toggle rules in-band and out of band on a per-request basis.

In other words, you get proper multi tenancy without losing control. CrowdSec stays focused on security logic and decision-making, while your platform orchestrates who gets which rules, on which paths, and under which conditions.

For teams that run SecOps for many tenants, that mix of centralized protection with dynamic, per-request configuration is what makes the difference between a WAF that scales and a WAF that slowly becomes unmanageable.

As Thomas Lohner, ScaleCommerce Chief Time To First Byte Officer, puts it:

CrowdSec significantly lowered the number of attacks we get daily, sparing both resources and human time while sharply raising overall security.

This real-world impact shows how CrowdSec’s approach translates into fewer alerts, faster response, and more manageable security at scale.

WRITTEN BY

You may also like

scalecommerce plummets operational costs and skyrockets efficiency with crowdsec
Success Story

ScaleCommerce Uses CrowdSec to Plummet Operational Costs and Skyrocket Efficiency

ScaleCommerce, a leading provider of high-performance and secure online shop solutions, uses CrowdSec to reduce operational costs and supercharge efficiency.

Scalable and Low-Friction Authentication for MSSPs with the CrowdSec IDPS
Use Case

Scalable and Low-Friction Authentication for MSSPs with the CrowdSec IDPS

Scaling can be an issue for smaller teams. See how we helped an MSSP user achieve scalable & low-friction authentication in 30 minutes with the CrowdSec IDPS.

The Importance of IDS and IPS When Exposing RDP Port 3389
Use Case

The Importance of IDS and IPS When Exposing RDP Port 3389

CrowdSec Partner Abdallah Toutoungi, CyberShield, explains the importance of IDS and IPS in safeguarding a system against malicious actors.