Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Teapot Laravel Package

aureola/laravel-teapot

Adds HTTP 418 I'm a teapot responses to Laravel apps, with middleware, route macros, and helpers for returning proper teapot status codes, configurable messages, and a tea-themed error page for playful APIs and demos.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish config

composer require aureola/laravel-teapot
php artisan vendor:publish --tag=teapot-config

The first place to look is the published config/teapot.php. That file is where you will find (depending on the version):

  • Whether the trap is enabled by default
  • The list of "suspicious" path patterns/needles the package matches (e.g. wp-login.php, phpmyadmin, .env, .git/config, admin.php)
  • The log file/channel it writes hits to (usually a dedicated channel so fail2ban can tail it cleanly)
  • Any whitelist/ignore configuration

Register the middleware

The package ships a middleware (something like TeapotMiddleware) that inspects incoming requests and returns 418 I'm a teapot when it matches. Wire it up in app/Http/Kernel.php:

// Laravel 10 and below – app/Http/Kernel.php
protected $middleware = [
    \Aureola\Teapot\Http\Middleware\Teapot::class,
    // ...rest
];

For Laravel 11+ register it in bootstrap/app.php via $middleware->append(...).

First use case

The immediate value: your Laravel app sits behind a public domain and is being hammered by /wp-login.php, /.env, /vendor/phpunit/... probes. Install the package, enable the middleware, and every scanner hit now:

  1. Gets an instant 418 instead of a 404/200
  2. Is written to a log line containing the offending IP
  3. Is picked up by fail2ban and banned at the firewall

Verify it works with curl before moving on:

curl -i https://your-app.test/wp-login.php
# Expect: HTTP/2 418
tail -n 5 storage/logs/teapot.log   # or whatever channel config points at

If you don't see a 418, the request didn't match your config's patterns — adjust config/teapot.php first, not the middleware.

Implementation Patterns

Global middleware is the typical placement. The whole point is to catch unsolicited traffic that never intended to hit a real route. Mounting it globally means /wp-admin, /.env, and /shell.php all get trapped before Laravel's router tries (and returns 404 for) them. Attaching it to a specific route defeats the purpose.

Honeypot route pattern. A common complement is exporting fake endpoints that lead nowhere:

// routes/web.php
Route::get('/wp-login.php', fn () => abort(418));
Route::post('/wp-login.php', fn () => abort(418));

Combine with the package's logging so a hit here looks identical to a hit on a scanner-pattern request.

Fail2ban integration pattern. The intended flow is log-file → fail2ban filter → jail. A typical filter on the package's log output:

# /etc/fail2ban/filter.d/laravel-teapot.conf
[Definition]
failregex = ^.*teapot.*<HOST>.*$
ignoreregex =

And a jail:

# /etc/fail2ban/jail.d/laravel-teapot.local
[laravel-teapot]
enabled  = true
filter   = laravel-teapot
logpath  = /var/www/your-app/storage/logs/teapot.log
maxretry = 1
bantime  = 86400
findtime = 600

maxretry = 1 is deliberate — a single deliberate scanner probe is enough signal. Tune only if you see false positives.

Pair with throttling, not instead of. Trap = ban signal. throttle: on normal routes = rate limiting. Keep them separate; don't let the teapot middleware swallow legitimate 429s.

Whitelisting pattern. Almost always needed for:

  • Your office/VPN IPs
  • Uptime monitors (Pingdom, BetterStack) — they occasionally probe weird paths
  • Health-check paths your load balancer hits (/healthz, /up)
  • Legitimate SEO crawlers if you allow them

Do this in config or a small middleware guard that short-circuits before the teapot check:

if (in_array($request->ip(), config('teapot.whitelist'))) {
    return $next($request);
}

Customising the trigger. If the package exposes the pattern list as config (likely), treat it as data. Add your own app-specific noise:

// config/teapot.php
'paths' => [
    'wp-login.php', 'wp-admin', 'xmlrpc.php',
    '.env', '.git/', 'phpmyadmin', 'admin.php',
    'vendor/phpunit', 'actuator', 'cgi-bin',
],

Logging to a dedicated channel. Don't share your application log. storage/logs/laravel.log is rotated, noisy, and multi-purpose; fail2ban gets unreliable if it has to disambiguate. Configure a teapot log channel in config/logging.php pointing at storage/logs/teapot.log so its format is stable.

Gotchas and Tips

The 418 status is the point — but not every client handles it well. Browsers render the "I'm a teapot" easter egg, some scraping libraries throw on it, and a few bots retry aggressively. Fine for scanners (that's the intent), but don't wire this middleware into anything a legitimate client (mobile app, API consumer) hits.

Reverse proxies will break IP logging. If you're behind Cloudflare, an ALB, or nginx, $request->ip() is your proxy until you configure TrustProxies. Fail2ban will ban your load balancer. Verify with:

curl -i -H "X-Forwarded-For: 1.2.3.4" https://your-app.test/.env
# Check the logged IP

Fix App\Http\Middleware\TrustProxies before going live.

Log rotation vs fail2ban. Once teapot.log rotates, fail2ban needs logrotate's postrotate hook (or copytruncate) to be told. Otherwise bans stop applying silently. If the package writes via Laravel's Log facade with daily channels, rotation happens on day boundaries — check fail2ban is following the rotated file too.

Middleware order matters. Put the teapot middleware after TrustProxies and before session/CSRF middleware. You don't want a scanner hit creating a session, writing to the DB, or hitting CSRF rejection before the trap catches it — that's wasted work and logs a false "attack" signature that has nothing to do with scanning.

Test your patterns, not just the middleware. A common failure is enabling the middleware but having a pattern list that doesn't match anything you're actually seeing. Check your nginx/access logs for real probe paths and add them to config/teapot.php.

Whitelist aggressively at the edge. 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, and your monitoring IPs should never be in the log. If the package supports CIDR entries in config, use them; otherwise add an allowlist check before the middleware.

Don't over-trust an obscure package. This package has 2 GitHub stars, 0 dependents, and is clearly pre-adoption. That's fine for a small trap, but:

  • Read the middleware source before shipping — it's probably ~100 lines; understand exactly what it matches and logs
  • Pin the version in composer.json
  • Test in staging with real traffic before production
  • Log format changes between versions will silently break your fail2ban regex — a composer update could quietly disable bans

Extension points to look for (verify in the source):

  • Whether the log writer is an interface you can swap (e.g. to emit as JSON for a log shipper)
  • Whether there's an event dispatched on trap (e.g. TeapotTriggered) — hook that to increment a counter, alert Slack, or trigger an immediate ban via fail2ban's socket
  • Whether the 418 response can be overridden (some prefer 403 or a blank 444-style close, which is cheaper for scanners)
  • Whether request matching is a strategy/contract you can replace with your own pattern engine

Debugging a non-firing trap:

# 1. Confirm middleware is registered
php artisan route:list --middleware=Teapot 2>/dev/null || true
# 2. Confirm config is loaded
php artisan tinker --execute="dump(config('teapot'));"
# 3. Confirm the request matches a pattern manually
curl -i https://your-app.test/wp-login.php
# 4. Check the log channel is actually written to
tail -f storage/logs/teapot.log

One last operational tip: ship the fail2ban jail config alongside the Laravel change in the same deploy. Half-shipped — trap enabled but jail not installed — means you're logging hundreds of scans a day that nobody acts on, which quickly becomes noise you ignore.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin