Phisherman enforces a per-IP rate limit on all requests using a Redis-backed counter. The limit is applied by the apiLimiter middleware registered in src/index.ts before any route handler runs.
Limit parameters
| Parameter | Value |
|---|
| Requests per window | 100 |
| Window duration | 15 minutes (900 seconds) |
| Scope | Per client IP address |
| Storage | Redis String (ratelimit:<ip>) |
How it works
On every incoming request, the middleware:
- Resolves the client IP from
req.ip, then falls back to the x-forwarded-for header, then req.socket.remoteAddress.
- Increments the Redis key
ratelimit:<ip> using INCR.
- If this is the first request (counter equals
1), sets a 900-second expiry on the key so the window resets automatically.
- If the counter exceeds
100, returns a 429 response immediately without calling the next middleware.
- Otherwise, calls
next() to proceed with the request.
Middleware source
export const apiLimiter = async (req: Request, res: Response, next: NextFunction) => {
const ip = req.ip || (req.headers["x-forwarded-for"] as string) || req.socket.remoteAddress;
const key = `ratelimit:${ip}`;
try {
const requests = await redis.incr(key);
if (requests === 1) {
await redis.expire(key, 900);
}
if (requests > 100) {
return res.status(429).json({
error: "Too many requests from this IP, please try again later.",
limit: 100,
current: requests
});
}
next();
} catch (err) {
console.error("Rate limit check failed:", err);
next();
}
};
When the limit is exceeded, the server responds with HTTP 429 and the following JSON body:
{
"error": "Too many requests from this IP, please try again later.",
"limit": 100,
"current": 101
}
current reflects the actual request count at the time of rejection, which may be higher than limit + 1 if requests arrive concurrently.
Reverse proxy support
Phisherman calls app.set("trust proxy", 1) in src/index.ts, which instructs Express to trust the first value in the X-Forwarded-For header as the real client IP. This means rate limiting correctly identifies the originating client when Phisherman is deployed behind a single reverse proxy (nginx, Caddy, a load balancer, etc.).
If you run Phisherman behind more than one proxy hop, adjust the trust proxy setting accordingly. See the Express behind proxies documentation for details.
app.set("trust proxy", 1) is set in src/index.ts:12. Without this setting, req.ip would always be the proxy’s IP address, and all traffic would share a single rate limit bucket.
If Redis is unavailable, the catch block calls next() instead of returning an error. This means the rate limit fails open — requests are not blocked when Redis is down. Design your deployment accordingly if you rely on rate limiting for abuse prevention.
The rate limit is per IP address, not per API key. Phisherman has no authentication layer, so all clients connecting from the same IP share the same 100-request budget. If you need per-client quotas, you will need to add an authentication middleware and adapt the key strategy in src/middleware/ratelimit.ts.