Recommended baseline
Use these as starting values, then adjust for your traffic:
Notes:
/c/:public_idonly needs a short public ID and an optional shortsrcquery parameter. A 2 KB URI ceiling is generous.- Mantis does not need request bodies on
/c/*;POSTis accepted only for clients that can only signal by POST. - If you intentionally embed long attribution in query strings, raise the URL
limit for
/c/*and keep the app’s storage caps in sync. - Node’s default HTTP request-header limit is 16 KiB. Railway’s public proxy allows 32 KB combined headers. The app stores request fields with caps above the Node default so normal accepted traffic is preserved.
- Mantis also enforces app-level body caps where it parses request bodies:
management JSON bodies are capped at 64 KiB, Apple Wallet log bodies at
32 KiB, and the dev inbox capture at 1 MiB. Oversized parsed bodies return
413 payload_too_large. - Stored hit headers are allowlisted and capped. Credential-shaped names such
as
authorization, cookies, session tokens, CSRF tokens, and API-key-looking headers are dropped before storage;x-mantis-*installer headers are kept.
Cloudflare Tunnel or Cloudflare in front of any host
Cloudflare is the cleanest place to enforce app-layer limits for public Mantis URLs. Rules only apply when the DNS record is proxied through Cloudflare.Custom rule: reject oversized public URLs
Dashboard path:- Cloudflare dashboard -> your zone -> Security -> WAF -> Custom rules.
- Create a rule named
mantis public URL limits. - Expression:
- Action:
Block. - Deploy.
MANTIS_PUBLIC_PATH, replace /c/ with that path.
Rate limiting rules
Dashboard path:- Security -> WAF -> Rate limiting rules.
- Create one rule for
/c/*:- Expression:
starts_with(http.request.uri.path, "/c/") - Characteristics: IP address
- Period: 60 seconds
- Requests: 120
- Mitigation timeout: 60 seconds
- Action: Block or Managed Challenge
- Expression:
- Create one rule for
/status/*:- Expression:
starts_with(http.request.uri.path, "/status/") - Characteristics: IP address
- Period: 60 seconds
- Requests: 240
- Mitigation timeout: 60 seconds
- Action: Block
- Expression:
- If Apple Wallet is enabled, create one rule for
/api/wallet/*:- Expression:
starts_with(http.request.uri.path, "/api/wallet/") - Characteristics: IP address
- Period: 60 seconds
- Requests: 120
- Mitigation timeout: 60 seconds
- Action: Block
- Expression:
http.request.body.size is an Enterprise
feature. On Free/Pro/Business plans, enforce body size in an origin proxy
such as nginx/Caddy or rely on Mantis not reading /c/* bodies.
Cloudflare references:
Railway
Railway has useful network limits, but not an app-layer WAF. Railway documents a 32 KB combined header limit, about 11,000 requests/sec per domain, and L4 DDoS mitigation; it explicitly recommends Cloudflare when you need WAF functionality. Recommended Railway setup:- Deploy Mantis on Railway as usual.
- Add a custom domain in Railway.
- Put that hostname in Cloudflare DNS with the orange cloud enabled.
- Add the Cloudflare custom and rate limiting rules above.
- Set
TRUST_PROXY_HEADERS=1only when Cloudflare is the public entry point.
MANTIS_DUPLICATE_LOG_LIMIT low and use hit
retention so known URLs cannot grow the database indefinitely.
Railway references:
Railway Redis/Valkey for shared rate limiting
Mantis does not require Redis/Valkey by default. Adding it on Railway means running another always-on service. Based on Railway’s current usage pricing:- RAM is billed at $10/GB-month.
- CPU is billed at $20/vCPU-month by actual usage.
- Volume storage is $0.15/GB-month.
- Hobby has a $5/month minimum that counts toward usage.
/c/*
hit adds a private-network round trip before the DB insert. Prefer Cloudflare
or provider edge limits first, then add Redis/Valkey only if you run multiple
Mantis replicas or need strict shared counters.
Fly.io
Fly’shttp_service.concurrency protects each Machine from too many concurrent
requests, but it is not a per-IP rate limiter or WAF. The example
deploy/fly.toml.example already uses request-based concurrency.
Recommended Fly setup:
- Keep
type = "requests"with a hard limit your VM can actually handle. - Use a Cloudflare-proxied custom domain for the public hostname if the Mantis URL will be exposed broadly.
- Add the Cloudflare rules above.
Render
Render provides DDoS protection automatically, but application-layer abuse is still your responsibility. Use a Cloudflare-proxied custom domain if you need per-path URL and rate limits before traffic reaches the service. Render reference:Tailscale Funnel
Tailscale Funnel is convenient, but it is not a full WAF. Use split mode fromtailscale.md so only public routes are exposed, and keep
dashboard/API traffic on the private Serve hostname.
For stricter public limits with Tailscale:
- Prefer Cloudflare Tunnel if you own a domain and want edge rules.
- Or put nginx/Caddy between the Funnel sidecar and Mantis, then point
Tailscale Serve/Funnel at that local proxy instead of
mantis:3000.
limit_req_zone in nginx’s http block. This
is intentionally not in the default compose file because it adds another moving
part for the common personal setup.
Local Docker
The compose file binds Mantis to127.0.0.1 by default. That is the best local
limit: no public edge exists. If you set MANTIS_BIND_HOST=0.0.0.0, put a
local reverse proxy in front before exposing it to untrusted networks.