Hey everyone! I just released CfBouncer, a small library that keeps your Cloudflare WAF block rules in sync with your Phoenix application.
The problem
If you’re using Cloudflare in front of your Phoenix app, you might want to block requests to paths that don’t exist in your application — vulnerability scanners probing for /wp-admin, /.env, /phpmyadmin, etc. Manually maintaining a WAF rule for this gets tedious and falls out of date as your app evolves.
What CfBouncer does
CfBouncer reads your Phoenix router, endpoint websockets, and static file paths, then generates a Cloudflare WAF expression that blocks everything except your known routes. It pushes this rule via the Cloudflare API.
For example, given routes like /, /users/:id, /auth/login and static paths like assets, robots.txt, it generates:
not (
http.request.uri.path eq "/"
or starts_with(http.request.uri.path, "/assets/")
or starts_with(http.request.uri.path, "/auth")
or starts_with(http.request.uri.path, "/live")
or starts_with(http.request.uri.path, "/robots.txt")
or starts_with(http.request.uri.path, "/users")
)
Usage
Add it to your mix deps:
{:cf_bouncer, "~> 0.1.0"}
Configure your router, endpoint, and Cloudflare credentials:
# config/config.exs
config :cf_bouncer,
router: MyAppWeb.Router,
endpoint: MyAppWeb.Endpoint,
static_module: MyAppWeb,
rule_description: "[CfBouncer] Block non-allowlisted paths"
# config/runtime.exs
config :cf_bouncer,
zone_id: System.get_env("CLOUDFLARE_ZONE_ID"),
api_token: System.get_env("CLOUDFLARE_API_TOKEN")
Then run it as part of your deploy:
mix cf_bouncer.sync
It’s idempotent — it only updates the rule when the expression actually changes. There’s also --dry-run to preview what would be pushed and --force to push regardless.
You can also add extra_paths for routes served outside Phoenix (proxied services, webhooks, etc.).
Cloudflare
On Cloudflare - your pushed rule will look like this:
Links
- Hex
- GitHub:
Would love to hear your feedback!























