gladios: stop HTTP floods at nginx
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;`
!Image 1: gladios’s mascot, a gladiator reading a book
Checking your browser
A quick check keeps bots away. It runs about once a week on each device.
Ready when you are.
Difficulty
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.
No pass yet
1. Challenge pagestatic file, 200 2. Browser solves the puzzle SHA-256 on Web Workers 3. Signed pass cookie 4. Reloads the same URL
Has a pass
1. njs checks it one HMAC 2. Your app
Forged, expired or from another network: the challenge page again, and a strike for fail2ban.
Bypassed
1. API clients, webhooks, monitors…your rules 2. 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.
1. ### Run the installer
`curl -fsSL https://gladios.dev/install.sh | sudo sh`
Installs 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 if `nginx -t` fails. Read it first; run it again to upgrade. 2. ### Guard a server
``` server { include snippets/gladios.conf; location /healthz { auth_request off; } # left out } ``` 3. ### 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, `realip` restoring 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 pass checked, then the app’s page | 35–36k/s | 1.7 ms | 0.14 cores | | With a forged pass checked, challenge page, strike | 24–26k/s | 2.4 ms | 0.19 cores | | Without a cookie challenge page, nothing to check | 35–36k/s | 1.7 ms | 0.15–0.18 cores | | `GET /.gladios/api/challenge`unthrottled | 59–61k/s | 1.0 ms | 0.11–0.17 cores | | `POST /.gladios/api/verify`unthrottled | 47–50k/s | 1.2 ms | 0.12–0.13 cores |
nginx pinned to 2 cores, oha on others, TLS keep-alive, 64 connections; Debian 12’s nginx 1.22 and njs 0.7.9 in an Apple M1 Pro’s Linux VM. nginx’s three processes took 11–12 MB. `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.