Basic Firewall
Basic Firewall evaluates every incoming request against a set of rules and allows, challenges or blocks it. All of that happens before Drupal routes the request, starts a session, authenticates anyone, or consults the page cache. Traffic you reject costs almost nothing to serve.
It is built on kanopi/firewall (Lite Firewall) and wraps every feature of that library in a full administrative interface, so nothing needs to be configured by hand in YAML.
Why evaluate this early
The module registers an HTTP middleware at priority 280. That places it after Drupal's reverse_proxy middleware, so the client IP address has already been resolved and can be trusted, and before page_cache, routing, sessions and authentication, so a rejected request never reaches any of them.
Running after the reverse proxy layer matters more than it sounds. If trusted proxies are not established, an attacker can forge an X-Forwarded-For header and walk straight through IP allow-lists, block-lists and per-IP rate limits. The module reports on the status page whether your proxy configuration makes the client IP trustworthy.
For sites that want the absolute earliest evaluation, an optional settings.php snippet runs the firewall before the Drupal container is built at all. It is not required, because the middleware is already ahead of anything expensive.
Configuration is the source of truth
Rules live in ordinary Drupal configuration, so they export, import and diff like anything else. That configuration is compiled into a single YAML file in the private directory, in the library's own native format, and that file is what the firewall reads at runtime. Loading the ruleset therefore costs a single file read, with no database query and no configuration API involved.
The compiled file is a cache. It is rebuilt whenever firewall settings are saved, whenever configuration is imported, and whenever caches are rebuilt, and it is safe to delete. Exported configuration records only which presets are enabled, not their several hundred individual patterns, so a preset never floods a configuration diff.
It always fails open
If the compiled file is missing, unreadable or invalid, the request is allowed through and the problem is reported on the status page and in Drupal's status report. A firewall misconfiguration will never be the reason your site is unreachable.
Because "allowed through" is exactly the state you would not notice, the status report distinguishes three separate questions: whether the configuration is compiled, whether it is current, and whether the library can actually load it. A file that exists but cannot be read is reported as an error, not as healthy.
Rule types
Rule type Matches on Notes IP address Client IP Single addresses, CIDR blocks,start-end ranges. IPv4 and IPv6.
Request / URL
Method, host, path, scheme, port, query, POST body, headers, cookies
The workhorse.
User agent
Automated flag, bot flag, device, browser, OS, brand, model
Parsed, not string-matched.
Geolocation
Country, continent, city, postal code, timezone
Needs a MaxMind City or Country database.
ASN
Autonomous system number or organisation
Needs a MaxMind ASN database. Effective against hosting and VPN traffic.
Rate limit
Requests per path per time window
Needs its own counter storage.
Vulnerability score
Method, country, network, attack patterns and user agent, summed
Catches requests only suspicious in combination.
IP reputation
AbuseIPDB abuse confidence score for the client IP
Needs a free API key. One cached lookup per visitor per day; fails open.
OWASP Core Rule Set
The full CRS ruleset
Requires a library release that ships it.
Every rule carries a response, and responses are evaluated in a fixed order:
- Allow. Let the request through and stop evaluating. Evaluated first, which makes an allow rule a reliable safety net.
- Challenge. Serve an interstitial the visitor must solve. Evaluated second.
- Block. Reject the request. Evaluated last.
Within a group, lower weights run first, so cheap checks (IP, path) can be put ahead of expensive ones (Core Rule Set, vulnerability scoring).
Challenges
A challenge serves a short interstitial instead of rejecting the request. A visitor who solves it receives a signed pass token and is not challenged again until it expires. It slows automated traffic down without shutting people out. Two providers ship:
- math. One addition, with no JavaScript, no external script and no third-party service.
- altcha. Proof of work, which costs a bot real CPU per solve, with single-use solutions.
A custom class implementing the library's provider interface plugs in Turnstile, hCaptcha or reCAPTCHA.
How long a solved challenge lasts is set per rule, so a visitor can be re-challenged on whatever interval suits the traffic. The signing secret is generated automatically on install and can be rotated from the interface.
Presets
The library ships maintained rule sets for WordPress endpoints, malicious URLs, malicious request patterns, rate limiting, and host-specific logging and storage. Switching one on includes it by reference rather than copying it in, so its rules update when you update the library, with no re-import step.
Because these rule sets are deliberately broad, every preset can be read before it is enabled: each rule, what it responds with, the patterns themselves, and the raw file with its comments.
Other modules can contribute their own presets through hook_basic_firewall_presets(), supplying either a YAML file or the configuration inline, and adjust the whole list with hook_basic_firewall_presets_alter().
Storage
When a rule blocks a request, the client is recorded so subsequent requests are rejected immediately without re-evaluating every rule.
Backend Use when File Single web node. No database needed, and works at the earliest possible point. Database Multiple web nodes sharing one block list. Uses Doctrine DBAL, not Drupal's database layer.Database storage can reuse Drupal's own credentials, which are read at compile time and written only into the compiled file in the private directory. They never enter Drupal configuration, so exports stay free of secrets. Alternatively, supply a connection DSN or individual connection parameters.
Rate limit counters have their own storage, chosen separately: a file, a database, or Redis.
Multisite
Table names and Redis keys are namespaced from the database connection's own prefix, so sibling sites sharing one database or one Redis instance keep separate block lists and separate rate limit counters.
Keeping secrets out of configuration
Every field that can hold a secret (API keys, the challenge signing secret, database passwords) accepts a token instead of the secret itself:
api_key: "%env(ABUSEIPDB_API_KEY)%" secret: "%file(/etc/firewall/hmac.key)%"
%env()% reads an environment variable. %file()% reads a file, for secrets that arrive as mounted files rather than variables, and has to be enabled deliberately in settings.php with an allowlist of directories it may read from.
Other features
- Log-only mode. The module installs with no rules and in log-only mode, so nothing is blocked until you say so. Every would-be block is recorded with the full request.
- Test a request. Describe a request in the interface and see which rule would match it, and why, without sending real traffic. It records nothing and blocks nobody.
- Exempt a role. Members of a chosen role can be skipped entirely. Because roles do not exist as early as the middleware runs, doing so adds a second, later evaluation point for authenticated traffic only.
- Logging through Monolog, because the firewall runs before Drupal's logging system exists. An option forwards those events into
dblogor syslog once Drupal has finished booting. - Blocked client list in the interface, with a lookup and a confirmed unblock.
- Advanced tab for raw library configuration, for anything the interface does not cover.
Drush
drush basic-firewall:status # what the firewall is doing right now drush basic-firewall:rules # rules in evaluation order drush basic-firewall:rebuild # recompile the configuration drush basic-firewall:check IP # is this address blocked? drush basic-firewall:block IP # block it drush basic-firewall:unblock IP # unblock it drush basic-firewall:blocked # list every blocked client drush basic-firewall:clear-blocked # empty the block list
All are aliased to bfw:.
Requirements
- Drupal 11
- PHP 8.3 or newer
kanopi/firewall^2.12 andkanopi/crs-engine^1.0, installed automatically with Composer- A configured private file system. This is where the compiled configuration and the blocked-client data live, and the module will not run without it.
Optional:
- MaxMind GeoLite2 databases, for the geolocation and ASN rule types. These are licensed separately and are not shipped.
- The
redisPHP extension, for Redis-backed rate limit counters.
Installation
composer require drupal/basic_firewall drush en basic_firewall
Then configure the private file system, if you have not already:
$settings['file_private_path'] = dirname($app_root) . '/private';Keep it outside the web root, then run drush basic-firewall:rebuild and visit Administration → Configuration → Security → Basic Firewall.
Getting started safely
A firewall can lock you out of your own site. The module is built so that cannot happen by accident:
- It installs in log-only mode with no rules. Nothing is blocked, and nothing matches, until you say so.
- Add your own address to an allow rule first, at a low weight. Allow rules run before everything else and a match ends evaluation immediately.
- Add the rules you actually want, and leave the mode on log-only.
- Read the log for a few days and look for anything legitimate.
- Switch the mode to Block once the log is clean.
If you do lock yourself out, either route works and neither needs the interface:
drush basic-firewall:unblock 203.0.113.10 # remove one client drush basic-firewall:clear-blocked # remove all of them
Or set $settings['basic_firewall_enabled'] = FALSE; in settings.php, which takes effect immediately and needs no database access at all.
How this differs from similar modules
Modules such as Ban and Perimeter act once Drupal is running. Basic Firewall evaluates before routing, sessions, authentication and the page cache, so rejected traffic never reaches them, and it can optionally evaluate before the container is built at all.
It is not a replacement for a WAF at the edge. If you have a CDN or reverse proxy that can filter traffic, filter it there first; this module is for the rules that need to live with the site, travel with its configuration, and be managed by the people who administer it.
Maintainers
Maintained by Kanopi Studios. Issues and merge requests are welcome in the issue queue.