tutorial

How to Monitor PlugAttack with Vigilmon

Add uptime monitoring, rate limit health checks, and alerts to Phoenix and Plug applications using PlugAttack for brute-force protection — covering throttle effectiveness, storage backend liveness, and auth route security.

How to Monitor PlugAttack with Vigilmon

PlugAttack is a Plug middleware for Phoenix and Plug applications that provides configurable rate limiting, IP throttling, and brute-force attack protection. It supports pluggable storage backends — ETS for single-node deployments and Redix for distributed Redis-backed rate limiting.

PlugAttack itself doesn't have a health endpoint to monitor. What you monitor is whether the protection is working: Is the rate limiting storage backend alive? Are authentication endpoints still responding under load? Is a brute-force attack currently happening that your team should know about?

This tutorial adds production observability to a Phoenix application using PlugAttack:

  • A health check endpoint that validates the PlugAttack storage backend
  • HTTP uptime monitoring with Vigilmon
  • Heartbeat monitoring to verify rate limiting is active
  • Slack alerts for security events and system health

Why Monitor a PlugAttack Application?

Rate limiting and attack protection are security-critical infrastructure. They fail in non-obvious ways:

  • Redis backend down: if PlugAttack uses Redix as its storage, a Redis failure means rate limit counters are lost. Depending on your failure mode configuration, this might mean all requests pass through unthrottled
  • ETS table cleared: a node restart wipes ETS-based rate limit state, giving attackers a clean slate
  • PlugAttack misconfiguration after deploy: a wrong merge changes a throttle rule, unintentionally blocking legitimate users or allowing brute-force attacks
  • Auth endpoint down: if PlugAttack is blocking requests to a login endpoint due to a misconfigured rule, legitimate users can't log in

External monitoring catches storage backend failures and alerts you when protected routes become unreachable.


Key Metrics to Monitor

| Metric | Why It Matters | |--------|---------------| | Rate limit storage backend | Is Redis/ETS available for counter tracking? | | Protected endpoint availability | Are throttled routes still responding for legitimate users? | | Auth endpoint response time | Is login unusually slow (could indicate attack volume)? | | 429 response rate | Are rate limits being triggered? How often? | | Storage backend latency | Is Redis slow enough to affect rate limit decisions? |


Step 1: Configure PlugAttack with a Redix backend

A typical PlugAttack setup with Redis storage:

# mix.exs
defp deps do
  [
    {:plug_attack, "~> 0.4"},
    {:redix, "~> 1.1"},
    {:jason, "~> 1.4"}
  ]
end
# lib/my_app_web/plug_attack.ex
defmodule MyAppWeb.PlugAttack do
  use PlugAttack

  # Allow local traffic
  rule "allow local", conn do
    allow conn.remote_ip == {127, 0, 0, 1}
  end

  # Throttle login attempts: 5 per 60 seconds per IP
  rule "throttle login by ip", conn do
    if conn.path_info == ["api", "auth", "login"] and conn.method == "POST" do
      throttle conn.remote_ip,
        period: 60_000,
        limit: 5,
        storage: {PlugAttack.Storage.Redix, MyApp.RedixConn}
    end
  end

  # Throttle API endpoints: 100 per minute per IP
  rule "throttle api by ip", conn do
    if String.starts_with?(conn.request_path, "/api/") do
      throttle conn.remote_ip,
        period: 60_000,
        limit: 100,
        storage: {PlugAttack.Storage.Redix, MyApp.RedixConn}
    end
  end
end

Add to your Phoenix endpoint:

# lib/my_app_web/endpoint.ex
defmodule MyAppWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :my_app

  plug MyAppWeb.Plugs.HealthCheck

  # ... other plugs ...

  # PlugAttack before the router
  plug MyAppWeb.PlugAttack

  plug MyAppWeb.Router
end

Start Redix in your supervision tree:

# lib/my_app/application.ex
def start(_type, _args) do
  children = [
    {Redix, {System.get_env("REDIS_URL"), [name: MyApp.RedixConn]}},
    # ... other children ...
    MyAppWeb.Endpoint
  ]

  Supervisor.start_link(children, strategy: :one_for_one)
end

Step 2: Add a health check endpoint

Create a health check plug that verifies the PlugAttack storage backend is operational:

# lib/my_app_web/plugs/health_check.ex
defmodule MyAppWeb.Plugs.HealthCheck do
  import Plug.Conn

  def init(opts), do: opts

  def call(%Plug.Conn{request_path: "/health"} = conn, _opts) do
    checks = %{
      database: check_database(),
      rate_limit_storage: check_rate_limit_storage()
    }

    status = if Enum.all?(checks, fn {_, v} -> v == :ok end), do: 200, else: 503

    conn
    |> put_resp_content_type("application/json")
    |> send_resp(status, Jason.encode!(%{status: status_label(status), checks: checks}))
    |> halt()
  end

  def call(conn, _opts), do: conn

  defp check_database do
    case Ecto.Adapters.SQL.query(MyApp.Repo, "SELECT 1", []) do
      {:ok, _} -> :ok
      {:error, _} -> :error
    end
  end

  defp check_rate_limit_storage do
    # Ping Redis to verify PlugAttack's storage backend is reachable
    case Redix.command(MyApp.RedixConn, ["PING"]) do
      {:ok, "PONG"} -> :ok
      _ -> :error
    end
  rescue
    _ -> :error
  end

  defp status_label(200), do: "ok"
  defp status_label(_), do: "degraded"
end

Test it:

curl http://localhost:4000/health
# {"status":"ok","checks":{"database":"ok","rate_limit_storage":"ok"}}

When Redis is down, the response changes to:

{"status":"degraded","checks":{"database":"ok","rate_limit_storage":"error"}}

This tells you PlugAttack is operating without persistent storage — rate limit counters are lost or falling back to per-process state.


Step 3: Set up HTTP monitoring in Vigilmon

Point Vigilmon at your health endpoint:

  1. Sign up at vigilmon.online
  2. Click New Monitor → HTTP
  3. Enter https://yourdomain.com/health
  4. Set check interval: 1 minute (paid) or 5 minutes (free)
  5. Add a keyword check: "rate_limit_storage":"ok" must be present
  6. Save

This gives you an alert within minutes if Redis goes down and your rate limiting stops functioning.

Monitor your protected auth endpoint separately

Add a second Vigilmon monitor specifically for your login endpoint:

  1. Click New Monitor → HTTP
  2. URL: https://yourdomain.com/api/auth/login
  3. Method: POST
  4. Body: {"email":"health@example.com","password":"invalid"}
  5. Expected status: 401 (not 429)
  6. Interval: 1 minute

This monitor verifies:

  • The auth endpoint is responding
  • PlugAttack is not blocking it for the monitoring probe IP
  • The endpoint returns 401 (rejected credentials) rather than 503 (server error)

If PlugAttack's throttle rule is misconfigured and starts blocking your health check IP, this monitor fails and you're alerted.


Step 4: Heartbeat monitoring for storage backend health

Add a periodic heartbeat that verifies the Redis storage backend is accessible and PlugAttack can make rate limit decisions:

# lib/my_app/rate_limit_monitor.ex
defmodule MyApp.RateLimitMonitor do
  use GenServer

  require Logger

  @check_interval :timer.minutes(5)

  def start_link(_), do: GenServer.start_link(__MODULE__, %{}, name: __MODULE__)

  def init(state) do
    schedule_check()
    {:ok, state}
  end

  def handle_info(:check, state) do
    case verify_rate_limit_storage() do
      :ok ->
        ping_heartbeat()
      {:error, reason} ->
        Logger.error("Rate limit storage unhealthy: #{inspect(reason)}")
    end

    schedule_check()
    {:noreply, state}
  end

  defp verify_rate_limit_storage do
    case Redix.command(MyApp.RedixConn, ["PING"]) do
      {:ok, "PONG"} -> :ok
      {:error, reason} -> {:error, reason}
    end
  rescue
    e -> {:error, e}
  end

  defp schedule_check, do: Process.send_after(self(), :check, @check_interval)

  defp ping_heartbeat do
    url = Application.get_env(:my_app, :vigilmon)[:rate_limit_heartbeat_url]
    if url, do: Req.get(url, receive_timeout: 5_000)
  end
end

Configure the heartbeat:

# config/runtime.exs
config :my_app, :vigilmon,
  rate_limit_heartbeat_url: System.get_env("VIGILMON_RATE_LIMIT_HEARTBEAT_URL")

Add MyApp.RateLimitMonitor to your supervision tree, then in Vigilmon create a Heartbeat Monitor:

  1. Click New Monitor → Heartbeat
  2. Name: "Rate Limit Storage"
  3. Expected interval: 6 minutes
  4. Copy the ping URL

If Redis goes down and the monitor process stops successfully pinging, Vigilmon alerts you — even if the HTTP health check degrades gracefully.


Step 5: Alerts via Slack

In Vigilmon, go to Notifications → New Channel → Slack and paste your Slack incoming webhook URL.

For a security-sensitive application, route rate-limit alerts to a dedicated security channel:

  1. api.slack.com/appsCreate New App → From scratch
  2. Enable Incoming WebhooksAdd New Webhook
  3. Pick your #security-alerts channel

When the storage backend fails:

🔴 DOWN: yourdomain.com/health
Keyword check failed: "rate_limit_storage":"ok" not found
Detected from: EU-West, US-East

When the auth endpoint becomes unexpectedly blocked:

🔴 DOWN: yourdomain.com/api/auth/login
Expected 401, got 429 Too Many Requests
Detected from: US-East

The second alert indicates a misconfigured throttle rule is blocking all requests to the login endpoint — a false positive that locks out legitimate users.


Step 6: Monitor Redis directly

Add a TCP monitor for your Redis instance to distinguish storage backend failures from application failures:

  1. Click New Monitor → TCP
  2. Host: your Redis hostname
  3. Port: 6379
  4. Interval: 1 minute

This separates:

  • Redis down (TCP monitor fails): PlugAttack cannot persist rate limit counters
  • Redix connection broken (heartbeat fails, TCP monitor passes): the connection pool needs recycling

For production Redis with TLS (Redis 6+), add the TCP monitor on port 6380 (TLS) rather than 6379.


Step 7: Status page and badge

Status page:

  1. Go to Status Pages → New Status Page in Vigilmon
  2. Add your health monitor, auth endpoint monitor, rate-limit heartbeat, and Redis TCP monitor
  3. Copy the public URL

Share the URL with your security team and in your incident runbook. When a brute-force alert fires, the status page is the first thing to check — it tells you immediately whether it's an attack hitting a healthy system or an attack hitting a degraded one.

README badge:

![Application Uptime](https://vigilmon.online/badge/your-monitor-slug)

What you've built

| What | How | |------|-----| | Health check endpoint | Plug that tests DB + PlugAttack storage backend | | Rate limit storage check | Redix.command/2 PING round-trip | | HTTP uptime monitoring | Vigilmon HTTP monitor → /health | | Keyword check | Verifies "rate_limit_storage":"ok" in response | | Auth endpoint monitor | Vigilmon HTTP monitor expecting 401 (not 429) | | Storage backend heartbeat | Periodic ping via RateLimitMonitor GenServer | | Direct Redis TCP check | Vigilmon TCP monitor on port 6379 | | Slack security alerts | Vigilmon Slack notification channel | | Status page | Vigilmon public status page |

PlugAttack keeps attackers out. Vigilmon keeps eyes on whether it's still working.


Next steps

  • Add rate limit hit counters to your application telemetry (via :telemetry) to see when throttles are actually firing in production
  • Use Vigilmon's response time history to detect auth endpoint slowdowns that might indicate a brute-force attack increasing load
  • Add separate TCP monitors for each Redis node in a cluster to detect partial failures
  • If you use multiple PlugAttack rules (login throttle, API throttle, IP blocklist), add separate health checks per rule to identify which rule is misbehaving

Get started free at vigilmon.online.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →