How to Run Anubis WASM Bot Filter as a Reverse Proxy [2026]

Install Anubis (WASM) in front of your app, forward real client IPs correctly, tune challenges to avoid blocking humans, and ship with a rollback plan.

Part of theDev Tools & AI Workflow series
nginx reverse proxy server rack — illustration for article on How to Run Anubis WASM Bot
Listen to this article
--:--

How to Run Anubis WASM Bot Filter as a Reverse Proxy [2026]

You’re going to have Anubis (WASM) bot filter running in front of your site as a reverse proxy, with defaults that block the obvious garbage without torching real users. Budget ~30–60 minutes if TLS and nginx are already done.

Close-up of server cooling fans in a vibrant data center

And yes, I’m going to say the keyword out loud because that’s why you’re here: Anubis wasm bot filter reverse proxy. Where it sits in the stack matters. Forwarding real client IPs matters more. And if you don’t have a rollback path, you’re not “deploying bot protection”. You’re rolling dice on your uptime.

Two opinions before we touch config files:

  1. Self-hosted bot protection is only worth the effort if you can measure false positives and roll back in minutes.
  2. Any bot filter that leans on JS or WASM will break somebody’s setup. That’s not a “maybe”. Your job is to decide who gets a bypass and write it down.

What is Anubis (WASM)?

Anubis (WASM) is a self-hosted bot mitigation reverse proxy that uses a WebAssembly-powered proof-of-work challenge to make large-scale scraping and abusive automation expensive while keeping normal human traffic usable.

a close up of a server in a server room

If you’ve ever hit Cloudflare’s “checking your browser” interstitial, you already get the trade. You add friction. You try hard to make it land on bots more than humans.

The difference is: with Anubis you’re running the thing yourself. You decide what gets logged, what gets challenged, what gets a pass, and what the failure mode looks like.

Anubis popped in 2026 because the WASM milestone got enough attention to hit the front page of Hacker News. That’s usually the moment something goes from “neat repo” to “operators are trying this on real traffic.” The other HN constant is also useful: the comments fill up with “this broke my browser” reports. Treat those as a test plan, not as drama.

What problem does it solve vs CDN bot protection?

CDN bot protection (Cloudflare-style) is seductive because it’s a toggle. And sometimes the toggle is the right answer. But you pay in a few predictable places:

  • Control: you get rules and dashboards, not source-level behavior.
  • Data: traffic and bot analytics live off your box.
  • Coupling: your mitigation posture is tied to a third-party edge and their idea of “normal”.

Anubis is for the class of sites that want “Cloudflare-like friction” without outsourcing the edge. Indie blogs. Docs sites. OSS project pages. Small SaaS dashboards. Any of these can get flattened by one aggressive scraper and suddenly your origin is a space heater.

If you want a threat taxonomy that isn’t vendor marketing, anchor decisions in OWASP’s automated threats list. The OWASP Automated Threats to Web Applications project is the closest thing we have to a shared vocabulary for credential stuffing, scraping, account aggregation, etc.

Where should Anubis sit in the stack (edge vs internal reverse proxy)?

There are two realistic placements:

a rack of electronic equipment in a dark room
  1. Internet → Anubis → nginx/app (Anubis is your edge)
  2. Internet → CDN/LB → Anubis → nginx/app (Anubis is an internal gate)

I prefer option 2 for most teams, including tiny ones.

If you already have a CDN, DDoS protection, or a managed load balancer, keep it. Let that layer eat volumetric junk. Put Anubis behind it to handle the traffic that looks like legitimate HTTP, but behaves like abuse.

The non-negotiable either way: Anubis must see the real client IP and scheme. If you get this wrong, your allowlists won’t work and your heuristics will be based on a proxy IP. You’ll block humans and let scrapers glide through.

This isn’t Anubis-specific. It’s reverse proxy 101. It’s also why the nginx docs keep hammering on forwarding headers and real IP handling. The NGINX reverse proxy guide is still the canonical reference.

A practical rule of thumb for 2026

  • Put Anubis at the edge only if you can tolerate “JS/WASM required” for most traffic.
  • Put Anubis behind an existing edge if you want a softer rollout, or you serve weird clients (enterprise proxies, text-mode browsers, accessibility tooling).

Also, don’t ignore the compatibility blast radius. The Anubis WASM rollout explicitly calls out that a no-JS solution is still in progress, and that extensions can break modern JS features. The default challenge page says it. Believe it.

Quick start: Deploy Anubis with Docker Compose

The fastest safe pattern is boring (good):

  • nginx terminates TLS
  • nginx forwards to Anubis over a private Docker network
  • Anubis forwards to your origin app

Here’s a minimal docker-compose.yml that’s actually runnable. You still need to replace the image/tag and env vars with whatever the upstream Anubis version expects.

yaml
services:
  anubis:
    image: techaro/anubis:latest
    container_name: anubis
    restart: unless-stopped
    ports:
      - "127.0.0.1:8081:8080"
    environment:
      # Example placeholders. Use the real Anubis config keys from upstream.
      ANUBIS_UPSTREAM: "http://app:8080"
      ANUBIS_LOG_LEVEL: "info"
    depends_on:
      - app

  app:
    image: your-app:latest
    container_name: app
    restart: unless-stopped
    expose:
      - "8080"

Three things I like about this layout:

  1. It keeps Anubis off the public interface (127.0.0.1 bind).
  2. It makes rollback stupid-simple. nginx can point back to the app.
  3. It forces you to think in “hop boundaries”. Every hop is where IP headers get lost.

Rolling upgrades without panic

If you’re upgrading from a pre-WASM setup or an earlier Anubis version, assume you need a canary.

The Anubis challenge page I hit while pulling upstream info reported running v1.28.0-pre1.0.20260906214259-7564aa1d8a85. When you see build strings like that, you should read it as “this is moving fast.” Move carefully.

For a canary on a small site, I’ll do 1% of traffic for 30 minutes, then 10% for 2 hours, then ramp. You don’t need a service mesh. You need two upstreams in nginx and the discipline to watch the numbers.

Deploy Anubis with systemd (without Docker)

If you don’t want containers on your edge box, systemd is fine. The operational posture is what matters:

  • run as a dedicated user
  • bind only on localhost or a private interface
  • make the upstream explicit
  • log somewhere you can actually query

Example unit file:

ini
[Unit]
Description=Anubis bot filter
After=network-online.target
Wants=network-online.target

[Service]
User=anubis
Group=anubis
ExecStart=/usr/local/bin/anubis \
  --listen 127.0.0.1:8081 \
  --upstream http://127.0.0.1:8080
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Even if your Anubis CLI flags don’t match exactly, you’re aiming for the same outcome: least privilege, tight network exposure, predictable restarts.

If you’re doing this on a single VM, keep the escape hatch obvious. I like having a “bypass” nginx server block sitting next to the protected one, disabled. When things go sideways, enabling it is a one-line change.

nginx config: Forward real client IPs (or you will block humans)

This is where most installs screw themselves.

If nginx sits in front of Anubis, nginx must forward:

  • X-Forwarded-For (client IP chain)
  • X-Forwarded-Proto (http/https)
  • Host (so Anubis can make host-based decisions if it supports them)

Minimal nginx site config:

nginx
server {
  listen 443 ssl;
  server_name example.com;

  # TLS config omitted

  location / {
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_pass http://127.0.0.1:8081;
  }
}

If you have another proxy in front (CDN, ALB, nginx ingress), you also need nginx to trust that upstream and set real_ip_header and set_real_ip_from correctly.

The exact directives depend on your topology. The point is boring and consistent with the NGINX reverse proxy docs: if you don’t handle forwarded headers and real IPs correctly, downstream services can’t make correct decisions.

Concrete failure mode: Anubis allowlists your office CIDR 203.0.113.0/24, but Anubis only sees 10.0.0.5 (your nginx container IP). Congrats. You built a bot filter that blocks your own team and still lets the scraper hammer you.

Choose what to protect: endpoints, not vibes

The “challenge everything” approach is how you end up with angry emails like “my RSS reader can’t access your blog.” And they’re right to be annoyed.

Map your routes to OWASP automation categories and dial strictness by cost and abuse potential:

  • Aggressive protection (challenge or block)
    • /login, /session, /oauth/* (credential stuffing)
    • /search (scraping + resource exhaustion)
    • high-cost endpoints like /api/report or /api/export
  • Moderate protection (soft challenge, rate-limit, or anomaly detection)
    • /signup (fake accounts)
    • /password-reset (abuse)
  • Leave open (or very light protection)
    • /robots.txt
    • /sitemap.xml
    • static assets (/assets/*, /favicon.ico)

A numeric heuristic that’s actually useful: if an endpoint costs you >50 ms of CPU or triggers a DB query that can go >100 ms p95, it deserves stricter controls. That’s the point where abusive automation turns into real money.

Also: don’t challenge your own CSS/JS. If the challenge page can’t load the assets it needs, you’ve created a self-inflicted outage.

Allowlists that don’t become bypass holes

Allowlists are where “don’t block humans” stops being a platitude.

You usually need four categories:

  1. Search engine bots (SEO and discoverability)
  2. Uptime monitors (or you’ll page yourself for no reason)
  3. CI/CD and deploy hooks (health checks, smoke tests)
  4. Internal networks (office/VPN)

1) Search engine bots

Do not allowlist by User-Agent alone. That’s cosplay security.

If you allowlist a bot, do it via:

  • a verified IP range, or
  • a verification method recommended by the platform

Even if you’re not using Cloudflare, their docs are a decent baseline for what “good hygiene” looks like. Start here: Cloudflare bot solutions.

And keep robots.txt and bot filtering separate in your head.

  • robots.txt is a polite request.
  • Anubis is enforcement.

2) Uptime monitors

Most monitors come from stable egress IPs. Allowlist those IPs and keep their check path unchallenged, like /healthz.

If you don’t have stable IPs, create a secret URL health check path. Yes, it’s security by obscurity. No, it’s not “real security.” It’s still a valid input into a larger posture.

Example: /healthz-<random-32-char>. Then allowlist that path in Anubis/nginx.

3) CI/CD

If your pipeline hits prod endpoints, you need an allowlisted CIDR or a machine-to-machine route that bypasses challenges.

This is where I’ll add a small rule in nginx: requests that have a private header from a known runner network go straight to origin. But you must lock it down by source IP. A header alone is a bypass hole waiting to happen.

4) Internal IP ranges

Internal allowlists should be CIDR-based and versioned.

This is one of those areas where the boring approach is correct. Put the CIDRs in a file, review it like code, and deploy it through your normal pipeline.

If you’re already thinking in terms of CI and rollback, you’ll probably enjoy my post on CI/CD tradeoffs for small teams. The exact tool doesn’t matter. The habit does.

Tune challenge strictness to reduce false positives

False positives are the entire game.

A hard challenge might be totally fine for /login. It’s not fine for your homepage.

Here’s a rollout pattern I trust for any traffic gate that can lock people out:

  1. Monitor-only (shadow mode) for 24 hours
  2. Soft challenge for 24–72 hours
  3. Hard challenge on endpoints that proved abusive
  4. Hard block only when you’ve seen the pattern repeat and you can explain it

If Anubis doesn’t support an explicit “monitor-only” mode, you can fake it:

  • log what you would have challenged
  • send a header like X-Anubis-Decision: would_challenge
  • don’t actually issue the challenge response

Numbers that matter in practice:

  • If your false-positive rate is >0.1% on a consumer-facing site, you will feel it.
  • If you’re B2B with low traffic, one blocked customer is the false-positive rate that matters.

Also, accept the physics. WASM/JS gating has UX and accessibility pitfalls. Cloudflare calls this out in their docs because they deal with it at scale. That’s not a knock on Cloudflare or Anubis. It’s just the reality of asking browsers to do extra work.

Accessibility + no-JS testing (and what fallback to provide)

If you deploy a WASM challenge, you owe users a fallback path. Period.

From the upstream Anubis challenge page itself:

  • it requires “modern JavaScript features”
  • privacy extensions can break it
  • “a no-JS solution is a work-in-progress”

So test the scenarios that will bite you:

  • JavaScript disabled (browser setting)
  • WebAssembly disabled (policy-driven environments)
  • Text-mode browsers (w3m/lynx)
  • Screen readers (NVDA/VoiceOver)
  • Corporate proxies that rewrite headers or block scripts

A simple checklist:

  1. Load your homepage with JS disabled. Confirm you get a helpful message, not a blank page.
  2. Try the same on /login.
  3. Confirm robots.txt and sitemap.xml still return 200 without a challenge.
  4. Confirm an allowlisted monitor can still hit /healthz.

Fallback options that actually work:

  • A documented address like support@… with “If you’re blocked by Anubis, include your IP and timestamp.”
  • A bypass cookie you can manually issue after a support request.
  • A static “accessibility bypass” page that explains what’s happening and how to proceed.

At Rise People, when I built a unified frontend platform with WCAG/AODA compliance baked in, the thing that saved us wasn’t a last-minute accessibility review. It was making compliance part of the scaffolding and defaults. Same idea here. If your bot filter’s default posture is “breaks no-JS clients,” you’re going to ship an accessibility incident.

If accessibility is an active focus for you, my CSS patterns post on accessibility is a useful reminder that “works on my machine” is not a definition of done.

Measure false positives: logs, labels, dashboards

If you don’t measure false positives, you’re not operating. You’re guessing.

At minimum, log these fields per request:

  • timestamp
  • request path
  • client IP (real client IP)
  • user agent
  • Anubis decision (allow, challenge, block)
  • challenge outcome (passed, failed, timeout)
  • response status

You want to answer, with numbers:

  • What percentage of requests were challenged in the last 1 hour?
  • What percentage of challenges failed?
  • Which top 10 paths are generating challenges?
  • What’s the challenge rate by country / ASN (if you have it)?

A concrete metric I like:

  • False-positive rate = (unique IPs that hit a “contact support / blocked” page) / (unique IPs that saw a challenge) over 24 hours.

Even on small sites, nginx logs plus a lightweight dashboard gets you most of the way.

If you already run OpenTelemetry for other systems, apply the same muscle here. I’ve written about production-grade observability for agents and services in production AI setups. Different domain, same operator mindset.

And if you care about cost, the “measure before you optimize” discipline is the same one I use for LLM cost. In both worlds, the bill comes from high-volume abuse plus retries you didn’t model.

Safe rollout + emergency recovery runbook

Most people only write the rollback plan after they need it. Don’t be most people.

Rollout plan

  1. Ship a bypass switch first. In nginx, keep an alternate upstream that routes directly to your origin.
  2. Deploy Anubis with monitoring. Don’t gate traffic yet.
  3. Enable challenges on 1–2 endpoints (start with /login and /search).
  4. Expand coverage based on evidence, not vibes.

When you lock users out

You will eventually lock somebody out. Plan for it.

  1. Flip nginx back to origin (bypass Anubis) within 5 minutes.
  2. Keep Anubis running, but in monitor-only mode, so you can inspect decisions.
  3. Add allowlist entries for the affected class (ASN, CIDR, verified bot) only after you confirm it’s legitimate.
  4. Write a short postmortem note. Even if it’s just for you.

On my own site, running a bunch of tools and datasets has taught me the same lesson repeatedly: you don’t get reliability by being clever. You get reliability by designing rollback paths.

If you want the same mindset in another domain, my prompt injection regression testing post is basically the same playbook. You don’t prevent every incident. You make failures cheap and reversible.

My take: self-hosted bot protection is becoming table stakes

2026 is the year “indie ops” got serious. Not because it’s trendy. Because scraping and automated abuse is now a default tax on publishing anything valuable.

Anubis is a pragmatic response. It doesn’t pretend to be magic. It makes automation expensive. It forces you to decide who you’re willing to accidentally block, and how you’ll handle it when you’re wrong.

My prediction: over the next 12 months, self-hosted bot mitigation is going to converge with the rest of the “run it yourself” infrastructure wave. The same folks who run a local LLM because they don’t want to ship data to third parties will also run their own traffic gates.

If you install Anubis this weekend, do one extra thing. Write the one-page “If you’re blocked” fallback page and link it directly from your challenge flow. That page will save you more credibility than any bot filter ever will.

Continue reading

A smartphone displaying music on a desk with computer monitors showing code

How to Set Up Self Hosted DevContainers [2026] (SSH + VS Code)

A reproducible “remote dev box” on one VM: hardened SSH, fast BuildKit-cached DevContainers, and a CI check that prevents IDE/CI drift. No Codespaces required.

Tailscale vs WireGuard 2026: Which VPN Actually Wins?

Tailscale vs WireGuard 2026: Which VPN Actually Wins?

I'd pick Tailscale for any team that needs a working mesh in under an hour, and raw WireGuard for infrastructure where you control every packet and can't hand keys to a third party. The fault line is control vs. convenience — and it's sharper than most comparisons admit.

Network servers are connected with cables.

I Turned a $200 MacBook into an Automated Linux Home Server [2026 Guide]

That old MacBook collecting dust in your drawer has a built-in UPS, solid thermal design, and enough horsepower to run Docker, Home Assistant, and media streaming. Here's exactly how to turn it into a headless Linux home server.

Abstract flowing lines on a dark background

Rust WASM vs TypeScript Performance: Why the 'Faster' Language Lost by 25% [2026]

A developer rewrote a Rust WASM JSON parser in TypeScript and it ran 25% faster. Here's why the JS/WASM bridge kills performance for the wrong workloads.

Cite this article
Kunal Ganglani (2026, September 7). How to Run Anubis WASM Bot Filter as a Reverse Proxy [2026]. Kunal Ganglani. Retrieved September 7, 2026, from https://www.kunalganglani.com/blog/anubis-wasm-bot-filter-reverse-proxy