PHPackages                             ramir1/laravel-bot2ban - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. ramir1/laravel-bot2ban

ActiveLibrary

ramir1/laravel-bot2ban
======================

.

v0.1.0(today)00MITPHPPHP ^8.3CI passing

Since Jul 24Pushed todayCompare

[ Source](https://github.com/ramir1/laravel-bot2ban)[ Packagist](https://packagist.org/packages/ramir1/laravel-bot2ban)[ Docs](https://github.com/ramir1/laravel-bot2ban)[ RSS](/packages/ramir1-laravel-bot2ban/feed)WikiDiscussions main Synced today

READMEChangelog (2)Dependencies (6)Versions (2)Used By (0)

Laravel Bot2Ban
===============

[](#laravel-bot2ban)

*[Русская версия](README.ru.md)*

A package for detecting suspicious requests typical of scanners, bots, and vulnerability-hunting tools (`/wp-login.php`, `/.env`, PHP shells, `xmlrpc.php`, `phpmyadmin`, etc).

The package **does not block IP addresses and does not keep state between requests**. Its job is to detect a suspicious request, log information about it to a dedicated log file, and return a preconfigured HTTP status code. The actual IP blocking at the OS/firewall level should be done by an external tool (e.g. [Fail2Ban](https://github.com/fail2ban/fail2ban)) that scans this log — ready-made config templates for it are included.

Installation
------------

[](#installation)

```
composer require ramir1/laravel-bot2ban
```

The package registers its own service provider and hooks the middleware in **globally** (via `Kernel::pushMiddleware`) — meaning every request is checked, including requests to routes that don't exist (`/wp-login.php` and similar paths that a Laravel app never has a real route for).

Publish the config files (individually or all at once):

```
php artisan vendor:publish --tag=bot2ban-config       # main config
php artisan vendor:publish --tag=bot2ban-signatures   # signature definitions
php artisan vendor:publish --tag=bot2ban-fail2ban      # fail2ban templates
php artisan vendor:publish --tag=bot2ban              # everything at once
```

How it works
------------

[](#how-it-works)

1. A request passes through `Bot2BanMiddleware`.
2. If the IP or URI matches the whitelist (`config('laravel-bot2ban.whitelist')`) — the request is passed through without any checks.
3. The request is matched against the signature registry (`SignatureRegistry`). Each signature matches IP/URI/User-Agent using one of 4 rule types (`exact`, `startswith`, `contains`, `endswith`) and has a severity level:
    - **Block** — an unambiguous sign of scanning/exploitation (e.g. `wp-admin`, `.env`, `phpmyadmin`, dangerous extensions). Returned immediately as soon as any Block signature matches.
    - **Warning** — a more generic pattern that could theoretically collide with a legitimate app route (e.g. the prefix `/backend`, `/dev`). Returned only if no Block signature matched.
4. On a match — the event is written to a dedicated log channel (`storage/logs/bot2ban-YYYY-MM-DD.log` by default, rotated daily), and the request is terminated with the configured HTTP status code (418 for Block, 403 for Warning by default).
5. Fail2ban on the server scans these files and bans the IP at the OS level — see [Fail2ban](#fail2ban).

### What you'll see when a signature triggers

[](#what-youll-see-when-a-signature-triggers)

Step 4 above happens via Laravel's `abort($code)`, exactly like any other `abort()` call in a Laravel app (404, 403, etc.) — it throws an `HttpException` that unwinds up through every middleware above `Bot2BanMiddleware` in the stack. That's expected and harmless: those middleware simply have a stack frame at their `return $next($request);` line, they aren't the source of anything.

What that renders as depends on `APP_DEBUG`:

- `APP_DEBUG=true` (typical local/`.loc` setup) — you'll see Laravel's full debug error page with the entire stack trace for the aborted request. **This is expected, not a bug** — it's the same page you'd get for any deliberately thrown `HttpException` while debugging.
- `APP_DEBUG=false` (production) — a clean response with the configured status code, no trace.

If you disable `APP_DEBUG` and still see the debug page, and the app runs under **Laravel Octane**: the already-running workers keep the config they booted with in memory and won't notice a `.env` change on disk. Re-run `php artisan config:cache` (if your deploy caches config) and then `php artisan octane:reload` (or fully restart Octane) — this is a general Octane behavior, not specific to this package (see [Laravel Octane](#laravel-octane) below).

Configuration
-------------

[](#configuration)

Main config — `config/laravel-bot2ban.php` (after publishing):

- `enable` — turns the package on/off (enabled by default only in `production`).
- `require_domain` — protection against bare-IP/wrong-domain access (checked only in production).
- `whitelist.ip` / `whitelist.uri` — exceptions that are passed through without detection (whitelist contains `/horizon` by default).
- `levels.warning` / `levels.block` — `enabled` (whether to enforce/abort) and `response_code` for each level.
- `log_channel` / `log_path` — where the log for fail2ban is written. The channel uses the `daily` driver, so the actual files are named `bot2ban-YYYY-MM-DD.log`.
- `log_retention_days` — how many days to keep old log files before Laravel deletes them automatically (default 14 — with headroom over the fail2ban jails' `bantime` of 7 days). Rotation/cleanup needs neither cron nor logrotate on the server.
- `debug_mode` — extra diagnostic messages via the app's standard logger (not the `bot2ban` channel).

Signatures
----------

[](#signatures)

The signature list **is not stored in the main config** — it lives in a separate registry (`SignatureRegistry`), assembled from `config/laravel-bot2ban-signatures.php` (after publishing) plus any signatures the app registers programmatically.

The core signature set — `Ramir1\LaravelBot2Ban\Signatures\CoreSignatures` — covers dangerous file extensions, WordPress paths, admin panels (phpMyAdmin/Adminer/cPanel), path traversal, `.env`/`.git`/ credential leakage, etc.

### Adding your own signatures

[](#adding-your-own-signatures)

Declaratively — after publishing `laravel-bot2ban-signatures.php`, add your own key:

```
return [
    'signatures' => \Ramir1\LaravelBot2Ban\Signatures\CoreSignatures::definitions() + [
        'project.internal-tool' => [
            'target' => 'uri',
            'type' => 'startswith',
            'patterns' => ['/internal-ops'],
            'level' => 'block',
        ],
    ],
];
```

Imperatively — via the `Bot2Ban` facade in `boot()` of any app service provider:

```
use Ramir1\LaravelBot2Ban\Facades\Bot2Ban;
use Ramir1\LaravelBot2Ban\Signatures\{Signature, SignatureTarget, MatchType, Severity};

Bot2Ban::register(new Signature(
    id: 'project.internal-tool',
    target: SignatureTarget::Uri,
    type: MatchType::StartsWith,
    patterns: ['/internal-ops'],
    level: Severity::Block,
));
```

### Rejecting signatures and safe package updates

[](#rejecting-signatures-and-safe-package-updates)

An app can **cancel (reject)** a specific core signature if it conflicts with a real app route (e.g. the site legitimately uses `/wp-admin` for its own purposes), without touching its definition — a single line in `config/laravel-bot2ban.php` is enough:

```
'signatures' => [
    'default_new_signature_policy' => 'disabled',
    'decisions' => [
        'core.wordpress-and-xmlrpc-paths' => 'rejected',  // this site legitimately uses /wp-admin
        'core.generic-admin-path-words'   => 'approved',
    ],
],
```

By default, `decisions` already contains `'approved'` for every core signature of the **currently installed** package version — protection works out of the box. The "safety net" only kicks in once you publish this config (`vendor:publish --tag=bot2ban-config`): the published copy is a snapshot frozen at publish time, so if a later package version adds a new core signature, its id won't be in your `decisions`, and it'll fall under `default_new_signature_policy` (`disabled` by default — detected and logged, but not enforced) instead of suddenly starting to block production right after `composer update`.

**Package update procedure:**

```
composer update ramir1/laravel-bot2ban
php artisan bot2ban:signatures:list
```

The command shows every known signature (core + custom) with its decision status (`approved`/`rejected`/`pending`) and effective behavior. New signatures marked `pending` can be explicitly approved/rejected by adding a line to `decisions`.

Nginx (optional)
----------------

[](#nginx-optional)

An optional fast-path in front of Laravel: nginx rejects the most obvious scanner probes (`.env`/`.git`, WordPress paths, known admin panel names, etc.) with `444` before PHP-FPM/Octane is even invoked — cheaper than letting `Bot2BanMiddleware` handle them. The signature list in this snippet is **deliberately small and static** — it should almost never need editing. Anything extensible or evolving (custom signatures, per-app whitelisting, Warning vs Block, approve/reject governance) belongs in the Laravel-side [signature registry](#signatures) instead, not here — don't try to keep the two in sync 1:1, that reintroduces the exact per-site config maintenance problem this package exists to avoid.

```
php artisan vendor:publish --tag=bot2ban-nginx
```

The file appears at `/nginx/antibot.conf` — include it inside your site's `server { }` block:

```
server {
    # ...
    include /path/to/app/nginx/antibot.conf;
}
```

Since this layer returns `444` directly from nginx, it never reaches Laravel — these requests won't show up in `bot2ban.log` or get caught by the `bot2ban-block`/`bot2ban-warning` fail2ban jails below. Publishing `--tag=bot2ban-fail2ban` also includes a separate matching filter/jail pair for this nginx layer (`bot2ban-nginx-instant`/`bot2ban-nginx-scan`, watching the nginx access log instead) — symlink them the same way as the other jails (see [Fail2ban](#fail2ban)):

```
ln -s /path/to/app/fail2ban/filter.d/bot2ban-nginx-instant.conf /etc/fail2ban/filter.d/
ln -s /path/to/app/fail2ban/filter.d/bot2ban-nginx-scan.conf    /etc/fail2ban/filter.d/
ln -s /path/to/app/fail2ban/jail.d/bot2ban-nginx.conf           /etc/fail2ban/jail.d/
```

Fail2ban
--------

[](#fail2ban)

Publish the templates:

```
php artisan vendor:publish --tag=bot2ban-fail2ban
```

The files will appear under `/fail2ban/{filter.d,jail.d}` — copy/symlink them into the system paths and adjust `logpath` for your deployment (multiple sites on one server — use a `*` glob, a single site — a specific path). The `logpath` template already contains the `bot2ban-*.log` mask (the log rotates daily, see `log_retention_days` above) — fail2ban picks up each new day's file on its own, nothing extra to configure:

```
ln -s /path/to/app/fail2ban/filter.d/bot2ban-block.conf   /etc/fail2ban/filter.d/
ln -s /path/to/app/fail2ban/filter.d/bot2ban-warning.conf /etc/fail2ban/filter.d/
ln -s /path/to/app/fail2ban/jail.d/bot2ban.conf           /etc/fail2ban/jail.d/
systemctl reload fail2ban
```

Log line format: `{ip} {LEVEL} {signature_id} [{date}] "{METHOD} {URI}" "{USER-AGENT}"`. The IP is the literal first token of the line (matches `` in fail2ban); `LEVEL`/`signature_id` are fixed, package-controlled tokens that come before any field an attacker could control (URI/User-Agent) — this protects the filter regexes from log injection.

- `bot2ban-block` — instant ban (1 attempt / 60 sec).
- `bot2ban-warning` — cumulative ban (5 attempts / 5 min).

Both — for 7 days (`bantime = 604800`), matching the nginx jails already in use on the server.

Laravel Octane
--------------

[](#laravel-octane)

The package is safe under Octane out of the box: `SignatureRegistry`, `SignatureGovernor`, `SignatureDetector` and `Bot2BanLogger` are singletons built from config **once per worker** (at bootstrap), not on every request, and hold no per-request state in their properties (only `readonly` data parsed from config). `Bot2BanMiddleware` has no properties of its own at all — all request data is local variables in `handle()`. The client IP is read via `$request->ip()` of the specific request, not via `$_SERVER`/static state. Verified with tooling: `phpstan.neon` has `checkOctaneCompatibility: true` enabled (Larastan's `OctaneCompatibilityRule`) — 0 findings.

Two things worth keeping in mind specifically because of the long-lived worker:

- **Only register custom signatures in a service provider's `boot()`** (`Bot2Ban::register(...)`), not in a controller/on every request — otherwise the signature registry will keep growing with every request within a worker until it's restarted.
- If you change `laravel-bot2ban.*` config at runtime (e.g. from an admin panel), already-resolved singletons (`SignatureDetector`/`SignatureGovernor`) won't pick up the change on their own — you need `php artisan octane:reload`. This is standard Octane behavior for any config-driven singleton, not specific to this package.

Development
-----------

[](#development)

Locally (via Docker, so you don't need PHP/Composer on the host):

```
docker compose build
docker compose run --rm php composer install
docker compose run --rm php vendor/bin/pest
docker compose run --rm php vendor/bin/pint --test
docker compose run --rm php vendor/bin/phpstan analyse
```

If PHP and Composer are already installed locally — the same commands without `docker compose run --rm php`: `composer install`, `composer test`, `composer pint`, `composer phpstan`.

CI (`.github/workflows/tests.yml`) runs all three checks on every push/PR.

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/971933?v=4)[Ramir1](/maintainers/Ramir1)[@ramir1](https://github.com/ramir1)

---

Top Contributors

[![ramir1](https://avatars.githubusercontent.com/u/971933?v=4)](https://github.com/ramir1 "ramir1 (2 commits)")

---

Tags

laravelantibot

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/ramir1-laravel-bot2ban/health.svg)

```
[![Health](https://phpackages.com/badges/ramir1-laravel-bot2ban/health.svg)](https://phpackages.com/packages/ramir1-laravel-bot2ban)
```

###  Alternatives

[unopim/unopim

UnoPim Laravel PIM

10.5k2.4k](/packages/unopim-unopim)[api-platform/laravel

API Platform support for Laravel

58174.6k17](/packages/api-platform-laravel)[ecotone/laravel

Ecotone for Laravel — CQRS, Event Sourcing, Sagas, Durable Workflows, and Outbox on top of Laravel Queue, via PHP attributes.

21318.6k3](/packages/ecotone-laravel)[codewithdennis/larament

Larament is a time-saving starter kit to quickly launch Laravel 13.x projects. It includes FilamentPHP 5.x pre-installed and configured, along with additional tools and features to streamline your development workflow.

3991.8k](/packages/codewithdennis-larament)[duncanmcclean/statamic-cargo

Comprehensive e-commerce addon for Statamic. Build bespoke e-commerce sites without the complexity.

3518.3k](/packages/duncanmcclean-statamic-cargo)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
