Examples 

This page collects ready-to-use recipes for common tasks. Each one is a complete config/system/phirewall.php file and returns the closure described in Configuration.

All examples use the ApcuCache store, the first choice on a single server. Pick the store that fits your setup on the Storage page. Rate limiting and bans need a store that keeps state between requests, so they do not work with the InMemoryCache.

Safelist office and monitoring IPs 

Let trusted clients through before any other rule runs, for example your office network, an uptime monitor, or a load test runner. A safelist match ends the check at once, so these clients are never rate limited or banned.

<?php
use Flowd\Phirewall\Config;
use Flowd\Phirewall\Store\ApcuCache;
use Psr\EventDispatcher\EventDispatcherInterface;

return function (EventDispatcherInterface $eventDispatcher): Config {
    $cache = new ApcuCache();
    $config = new Config($cache, $eventDispatcher);

    $config->safelists->ip('office-and-monitoring', [
        '203.0.113.10',
        '198.51.100.0/24',
    ]);

    return $config;
};
Copied!

Ban brute-force logins with allow2ban 

Ban a client that posts to the frontend login again and again. An allow2ban rule with a filter counts the requests that match the filter and lets them through, then bans the client once it crosses the threshold within the period. A fail2ban rule is the wrong tool here: it treats every match as malicious and answers each login post with a 403, which locks out real users. Replace /login with the path of the page that holds your felogin form.

<?php
use Flowd\Phirewall\Config;
use Flowd\Phirewall\Store\ApcuCache;
use Psr\EventDispatcher\EventDispatcherInterface;

return function (EventDispatcherInterface $eventDispatcher): Config {
    $cache = new ApcuCache();
    $config = new Config($cache, $eventDispatcher);

    $config->allow2ban->add(
        name: 'felogin-brute-force',
        threshold: 5,
        period: 300,
        banSeconds: 900,
        filter: fn($request) => $request->getMethod() === 'POST'
            && str_starts_with($request->getUri()->getPath(), '/login'),
    );

    return $config;
};
Copied!

This bans a client for 15 minutes after 5 login posts within 5 minutes.

Ban request floods with allow2ban 

An allow2ban rule counts every request from a client, not only the ones that match a filter, and bans the client once it crosses the threshold in the period. Use it as a blunt guard against request floods. Keep the threshold well above what a normal visitor reaches, so real people are never caught.

<?php
use Flowd\Phirewall\Config;
use Flowd\Phirewall\Store\ApcuCache;
use Psr\EventDispatcher\EventDispatcherInterface;

return function (EventDispatcherInterface $eventDispatcher): Config {
    $cache = new ApcuCache();
    $config = new Config($cache, $eventDispatcher);

    $config->allow2ban->add(
        name: 'request-flood',
        threshold: 240,
        period: 60,
        banSeconds: 600,
    );

    return $config;
};
Copied!

This bans a client for 10 minutes once it sends more than 240 requests in 60 seconds.

Throttle a search or JSON endpoint 

Limit how often a client may call an expensive endpoint, for example a site search or a JSON API. A throttle allows a fixed number of requests per period and answers further requests with a 429 response and a Retry-After header. The scope keeps the counter to the endpoint you name, so browsing the rest of the site does not add to it.

<?php
use Flowd\Phirewall\Config;
use Flowd\Phirewall\Config\ClosureRequestMatcher;
use Flowd\Phirewall\Config\Rule\ThrottleRule;
use Flowd\Phirewall\Store\ApcuCache;
use Psr\EventDispatcher\EventDispatcherInterface;

return function (EventDispatcherInterface $eventDispatcher): Config {
    $cache = new ApcuCache();
    $config = new Config($cache, $eventDispatcher);

    $config->throttles->addRule(new ThrottleRule(
        name: 'search-endpoint',
        limit: 20,
        period: 60,
        keyExtractor: null,
        scope: new ClosureRequestMatcher(
            fn($request) => str_starts_with($request->getUri()->getPath(), '/api/search'),
        ),
    ));
    $config->enableRateLimitHeaders();

    return $config;
};
Copied!

enableRateLimitHeaders() adds the X-RateLimit-* headers so clients can see how much of their budget is left.

Combine a preset with your own rules 

Apply a ready-made preset and add your own rules on top. with() returns a new configuration, so assign it back to $config. Rules you add afterwards run alongside the preset. See Presets for the available packages.

<?php
use Flowd\Phirewall\Config;
use Flowd\Phirewall\Store\ApcuCache;
use Flowd\PhirewallPresetOwaspCrs\ParanoiaLevel;
use Flowd\PhirewallPresetOwaspCrs\Presets as OwaspPresets;
use Psr\EventDispatcher\EventDispatcherInterface;

return function (EventDispatcherInterface $eventDispatcher): Config {
    $cache = new ApcuCache();
    $config = new Config($cache, $eventDispatcher);

    $config = $config->with(OwaspPresets::blocklist(ParanoiaLevel::Level1));

    $config->blocklists->add(
        name: 'cms-scanner-paths',
        callback: fn($request): bool => (bool)preg_match('#^/(wp-admin|wp-login\.php|xmlrpc\.php)(/|$)#i', $request->getUri()->getPath()),
    );

    return $config;
};
Copied!

Send a custom response for blocked requests 

Replace the default 403 body with your own message and headers. The closure receives the rule name, the rule type, and the request, and returns a PSR-7 response. Keep the 403 status so crawlers and caches treat the request as blocked.

<?php
use Flowd\Phirewall\Config;
use Flowd\Phirewall\Config\Response\ClosureBlocklistedResponseFactory;
use Flowd\Phirewall\Store\ApcuCache;
use Psr\EventDispatcher\EventDispatcherInterface;
use TYPO3\CMS\Core\Http\ResponseFactory;
use TYPO3\CMS\Core\Http\StreamFactory;

return function (EventDispatcherInterface $eventDispatcher): Config {
    $cache = new ApcuCache();
    $config = new Config($cache, $eventDispatcher);

    $config->blocklists->add(
        name: 'block-wp-admin',
        callback: fn($request) => str_starts_with(strtolower($request->getUri()->getPath()), '/wp-admin'),
    );

    $config->blocklistedResponseFactory = new ClosureBlocklistedResponseFactory(
        fn(string $rule, string $type, $request) => (new ResponseFactory())
            ->createResponse(403)
            ->withHeader('Content-Type', 'text/plain; charset=utf-8')
            ->withBody((new StreamFactory())->createStream('Blocked by the firewall.')),
    );

    return $config;
};
Copied!

Lock the site down temporarily 

Close the site to everyone except a few addresses, for example during maintenance. The safelist is checked before the blocklist, so listed clients pass while the catch-all blocklist answers every other request with a 403. Remove both rules when you are done.

<?php
use Flowd\Phirewall\Config;
use Flowd\Phirewall\Store\ApcuCache;
use Psr\EventDispatcher\EventDispatcherInterface;

return function (EventDispatcherInterface $eventDispatcher): Config {
    $cache = new ApcuCache();
    $config = new Config($cache, $eventDispatcher);

    $config->safelists->ip('maintenance-access', [
        '203.0.113.10',
    ]);
    $config->blocklists->add(
        name: 'lockdown',
        callback: fn($request) => true,
    );

    return $config;
};
Copied!

Forward firewall events to the TYPO3 log 

The extension records events in its own log (see Statistics). To also send them to the TYPO3 logging framework, register a PSR-14 listener for the phirewall events. This is a listener class in your own extension or site package, not part of the phirewall.php file.

The listener logs every blocked request:

<?php
declare(strict_types=1);

namespace MyVendor\MySitePackage\EventListener;

use Flowd\Phirewall\Events\BlocklistMatched;
use Psr\Log\LoggerInterface;

final class LogBlockedRequests
{
    public function __construct(
        private readonly LoggerInterface $logger,
    ) {}

    public function __invoke(BlocklistMatched $event): void
    {
        $this->logger->warning('Firewall blocked a request', [
            'rule' => $event->rule,
            'path' => $event->serverRequest->getUri()->getPath(),
        ]);
    }
}
Copied!

Register it in your extension's Configuration/Services.yaml:

MyVendor\MySitePackage\EventListener\LogBlockedRequests:
    tags:
        - name: event.listener
          identifier: 'my-firewall-log/blocklist-matched'
Copied!

TYPO3 reads the event to listen for from the type of the __invoke argument. On TYPO3 13 you can use the #[AsEventListener] attribute instead of the tag. The other events live in the Flowd\Phirewall\Events namespace, for example ThrottleExceeded and Fail2BanBanned.