Skip to content

Build1 publisher2 min readPublished

Every request that clears the edge still costs a PHP worker

A dev.to layering guide for self-hosted Laravel admins runs from ufw defaults through ModSecurity to a five-per-minute login limiter. Both fail2ban and that limiter depend on resolving the real client IP.

The Engineer · Build desk

Illustration accompanying Every request that clears the edge still costs a PHP worker

What happened

  • A dev.to guide for self-hosted Laravel admins stacks five controls: host firewall, reverse proxy with TLS and headers, a WAF ahead of PHP, fail2ban, and named Laravel rate limiters.
  • Its baseline ufw policy denies all incoming traffic, allows outgoing, and reopens only OpenSSH, 80/tcp and 443/tcp before the firewall is enabled.
  • MySQL, Redis, Elasticsearch, phpMyAdmin and queue dashboards stay off public ports, bound to localhost or a private network, with cloud security groups matching the host rules.
  • At the app layer the guide defines a named limiter capped at five per minute keyed on the request IP, attached to the login POST route as throttle:login.
  • fail2ban reads nginx or Caddy logs for repeated POST /login failures and contact endpoint floods and turns them into firewall drops.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Five per minute works out to 300 attempts an hour per address, and a distributed client runs that limit from every address in its pool.
  • exposure An origin still reachable on app ports makes the managed CDN rules optional for the attacker, leaving the host firewall and the app limiters as the only controls in the path.
  • cost Running OWASP CRS in blocking mode costs tuning time against a rich CMS editor. The guide budgets for DetectionOnly first and narrow whitelists for uploads and Livewire endpoints.
  • decision Adopting a CDN forces a rewrite of the jail filters and the proxy trust config, or the bans land on the CDN edge instead of the offender.

A request that gets past the edge still occupies a PHP-FPM worker before the login controller can reject it. The dev.to guide states it as "Laravel still pays for every request that reaches PHP" [2]. The post does not measure that cost in workers or milliseconds, so read it as a design argument.

Both the app limiter and fail2ban depend on one value: the client address. Laravel's `TrustProxies` has to accept `X-Forwarded-For` and `X-Forwarded-Proto` from your proxy only, and the guide warns that wrong trust settings break HTTPS detection and rate-limit IP keys [6]. A limiter keyed on `$request->ip()` can only be as accurate as that header chain. fail2ban has the same dependency from the other end, because once a CDN is in front, the jail filters need to ban the real client address and not the CDN edge [12].

`RateLimiter::for('login', ...)` with `Limit::perMinute(5)->by($request->ip())`, attached to the login POST as `throttle:login` [13], allows 300 attempts an hour from one address [1]. Point a thousand-address proxy pool at the same rule and it permits 300,000 an hour [2]. The post also wants stricter limits on password reset and 2FA challenge routes, because "Attackers often pivot there after login is locked" [14].

For a small VPS with one CMS the guide calls a CDN WAF plus host firewall often enough; air-gapped hosts get ModSecurity on the proxy; high-traffic APIs combine an edge WAF with precise Laravel throttles "so good API keys are not painted like scrapers" [10]. The first of those holds only if the origin accepts traffic on app ports from the CDN alone, since an open origin IP lets attackers bypass the WAF entirely [9]. If you cannot enforce that, the host firewall and the named limiters are the only throttling left.

The ufw sequence ends with a reminder to confirm SSH still works before you close the laptop [18]. Someone has evidently done it the other way. The post discloses that its authors build LaraDashboard, an open-source Laravel admin and CMS, and says the patterns apply to any Laravel app you own [15].

What to watch

  • Whether the promised public API limiter guidance arrives; the published text stops mid-sentence at "Public APIs need".
  • Whether LaraDashboard ships the named login, reset and 2FA limiters as route defaults rather than as documentation.
  • Whether CRS whitelist recipes for Livewire admin endpoints get published, since that tuning is the cost of blocking mode.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories