Stop bots and HTTP floods before they reach your app
gladios adds a “Checking your browser” page to the nginx you already run. People pass in about a second, once a week. Bots and flood tools that don’t run JavaScript never get past it, and turning them away costs nginx no more than serving a file.
- No service or proxy to run
- No third party sees your traffic
- Free and open source
curl -fsSL https://gladios.dev/install.sh | sudo sh
Then one line per server: include snippets/gladios.conf;
Checking your browser
A quick check keeps bots away. It runs about once a week on each device.
- Solved in
- –
- Hashes tried
- –
- Threads
- –
The real solver, slowed down so you can watch; the numbers are real. See the whole page →
Floods don’t run JavaScript. Browsers do.
A browser spends ~65,000 hashes on a puzzle; nginx checks the answer with one. It all runs in nginx and its JavaScript module (njs), so there’s no proxy hop and no process to keep alive. Same idea as Anubis, minus the service.
- Challenge pagestatic file, 200
- Browser solves the puzzleSHA-256 on Web Workers
- Signed pass cookie
- Reloads the same URL
- njs checks itone HMAC
- Your app
Forged, expired or from another network: the challenge page again, and a strike for fail2ban.
- API clients, webhooks, monitors…your rules
- Your app
Stateless
Puzzles and passes are HMACs. Nothing is stored, so every worker and host sharing the secret agrees.
Bound to the network
A pass only works from the address (or IPv6 /64) that solved it, so it can’t be shared across a botnet.
Cheap where it counts
No cookie costs a static file. A pass-shaped cookie costs one HMAC. Nothing else reaches njs.
Forgers get banned
Forged passes leave strikes for fail2ban, with ban digests to Slack, Discord or a webhook.
Installed in a minute
Debian, Ubuntu, Alpine or nginx.org’s packages. The installer takes care of njs.
Run the installer
curl -fsSL https://gladios.dev/install.sh | sudo shInstalls njs if it’s missing, copies gladios into place and writes a secret and
/etc/gladios/settings.conf. It never reloads nginx, and backs out ifnginx -tfails. Read it first; run it again to upgrade.Guard a server
server { include snippets/gladios.conf; location /healthz { auth_request off; } # left out }Reload nginx
sudo nginx -t && sudo systemctl reload nginx
Who gets checked
Anything that can’t run a browser needs a bypass, in /etc/gladios/settings.conf:
map $request_uri $gladios_bypass {
default 0;
~^/\.well-known/ 1; # ACME and friends
~^/api/ 1;
}
Requirements
- nginx with njs 0.7.9+. On Ubuntu 22.04, use nginx.org’s packages.
- HTTPS: the pass cookie is
Secure. - Behind a proxy,
realiprestoring the client’s address. - Optional: fail2ban, and Python 3.8+ for ban notifications.
Tens of thousands of requests a second, on two cores
The check runs inside the nginx worker that already has the request.
| Request | Most served | p50 | CPU at 1,000 req/s |
|---|---|---|---|
| With a valid passchecked, then the app’s page | 35–36k/s |
1.7 ms | 0.14 cores |
| With a forged passchecked, challenge page, strike | 24–26k/s |
2.4 ms | 0.19 cores |
| Without a cookiechallenge page, nothing to check | 35–36k/s |
1.7 ms | 0.15–0.18 cores |
GET /.gladios/api/challengeunthrottled |
59–61k/s |
1.0 ms | 0.11–0.17 cores |
POST /.gladios/api/verifyunthrottled |
47–50k/s |
1.2 ms | 0.12–0.13 cores |
test/bench/run.sh runs it on yours.Make it yours
Drop a mascot.webp (or png, svg, jpg, gif) in /etc/gladios/site/ to replace the
gladiator on the default page, or put a whole index.html of your own there.
Upgrades never touch it.
What it won’t do
- Stop a real headless browser. It pays the work, as with any check like this; per-network passes and rate limits make that costly at scale.
- Let visitors without JavaScript in.
- Show monitors your app is down: the page answers 200, so bypass them.