tutorial

How to Monitor Plug.Cowboy with Vigilmon

Learn how to add health checks, uptime monitoring, and alerts to your Plug.Cowboy application with Vigilmon.

How to Monitor Plug.Cowboy with Vigilmon

Plug.Cowboy is the adapter that bridges the Plug HTTP middleware specification and the Cowboy web server — the traditional HTTP/1.1 and WebSocket server used by Phoenix applications. Every request that flows into a Phoenix app passes through this adapter before reaching your application code.

Because Plug.Cowboy sits at the very entry point of your stack, failures here are total: if the adapter hangs, misconfigures, or starves of acceptor processes, the entire application stops responding — even if the rest of your BEAM node is perfectly healthy. External monitoring is the only way to detect these failures from outside the node boundary.

This tutorial shows you how to add production observability to a Plug.Cowboy application:

  • A lightweight health check plug
  • HTTP uptime monitoring with Vigilmon
  • Alerts on acceptor pool exhaustion and connection-level failures
  • Heartbeat monitoring for background workers
  • A public status page

Step 1: Add a health check plug

Plug.Cowboy starts a Cowboy listener with a Plug pipeline. Add a dedicated health check plug that responds before your main application pipeline:

# lib/my_app/plugs/health.ex
defmodule MyApp.Plugs.Health do
  import Plug.Conn

  def init(opts), do: opts

  def call(%Plug.Conn{request_path: "/health"} = conn, _opts) do
    checks = %{
      cowboy: check_cowboy(),
      memory: check_memory()
    }

    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: (if status == 200, do: "ok", else: "degraded"), checks: checks}))
    |> halt()
  end

  def call(conn, _opts), do: conn

  defp check_cowboy do
    # Verify Cowboy listener is running and accepting connections
    listeners = :ranch.info()
    if length(listeners) > 0, do: :ok, else: :error
  rescue
    _ -> :error
  end

  defp check_memory do
    case :memsup.get_system_memory_data() do
      [] -> :ok
      data ->
        total = Keyword.get(data, :total_memory, 1)
        free = Keyword.get(data, :free_memory, total)
        used_pct = (total - free) / total * 100
        if used_pct < 90, do: :ok, else: :error
    end
  end
end

Wire it into your Cowboy router definition:

# lib/my_app/router.ex
defmodule MyApp.Router do
  use Plug.Router

  plug MyApp.Plugs.Health
  plug :match
  plug :dispatch

  get "/", do: send_resp(conn, 200, "Hello from Plug.Cowboy!")

  match _, do: send_resp(conn, 404, "Not found")
end

Start Cowboy with Plug via your application supervisor:

# lib/my_app/application.ex
defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    children = [
      {Plug.Cowboy, scheme: :http, plug: MyApp.Router, options: [
        port: String.to_integer(System.get_env("PORT") || "4000"),
        # Configure the acceptor pool — monitor exhaustion here
        num_acceptors: 100
      ]}
    ]

    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

Verify it works:

mix run --no-halt
curl http://localhost:4000/health
# {"status":"ok","checks":{"cowboy":"ok","memory":"ok"}}

Step 2: Monitor acceptor pool metrics

Cowboy uses Ranch under the hood for socket acceptance. A saturated acceptor pool is one of the most common causes of intermittent Plug.Cowboy failures — new connections queue and eventually time out while existing requests complete normally.

Add acceptor pool details to your health response:

defp check_cowboy do
  try do
    info = :ranch.info(:http)
    active = info[:active_connections] || 0
    max = info[:max_connections] || 1024
    acceptors = info[:num_acceptors] || 0

    cond do
      acceptors == 0 -> :error
      active / max > 0.9 -> :error  # over 90% connection capacity
      true -> :ok
    end
  rescue
    _ -> :error
  end
end

This surface-level check lets Vigilmon alert you before connections start refusing, rather than after.


Step 3: Set up HTTP monitoring in Vigilmon

With a health endpoint running, point Vigilmon at it:

  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. Under Advanced, set the expected response body to contain "ok" and response time threshold to 2000 ms
  6. Save

Vigilmon probes from multiple geographic regions. For Plug.Cowboy deployments this matters because:

  • A hung acceptor pool fails new connections while keeping existing ones alive — your internal health check may pass but external probes time out
  • Multi-region probes catch regional routing or DNS failures independently of application state

Step 4: Alerts via Slack

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

Enable the notification channel on your monitor. When Cowboy stops accepting connections, Vigilmon sends:

🔴 DOWN: yourdomain.com/health
Status: Connection timeout
Detected from: EU-West, US-East
3 minutes ago

To reduce noise, configure a confirmation delay in Vigilmon — require the failure to be seen from at least two regions before alerting. This prevents false positives from transient probe network issues.


Step 5: Heartbeat monitoring for background processes

Plug.Cowboy applications often run background workers alongside the HTTP server. These can silently fail without affecting the HTTP layer. Add heartbeat monitoring to critical workers:

# lib/my_app/workers/cleanup_worker.ex
defmodule MyApp.Workers.CleanupWorker do
  use GenServer

  require Logger

  @interval :timer.minutes(30)

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

  @impl true
  def init(state) do
    schedule_tick()
    {:ok, state}
  end

  @impl true
  def handle_info(:tick, state) do
    case run_cleanup() do
      :ok ->
        ping_heartbeat()
        Logger.info("Cleanup complete")
      {:error, reason} ->
        Logger.error("Cleanup failed: #{inspect(reason)}")
        # No ping → Vigilmon alerts after the window expires
    end

    schedule_tick()
    {:noreply, state}
  end

  defp schedule_tick, do: Process.send_after(self(), :tick, @interval)

  defp run_cleanup, do: :ok  # your logic

  defp ping_heartbeat do
    url = Application.get_env(:my_app, :vigilmon)[:cleanup_heartbeat_url]
    if url do
      case Req.get(url, receive_timeout: 5_000) do
        {:ok, _} -> :ok
        {:error, reason} -> Logger.warning("Heartbeat failed: #{inspect(reason)}")
      end
    end
  end
end

In Vigilmon, create a Heartbeat Monitor for each worker:

  1. Click New Monitor → Heartbeat
  2. Set the expected interval (e.g. 35 minutes for a 30-minute worker, giving a 5-minute grace period)
  3. Copy the unique ping URL
  4. Set VIGILMON_CLEANUP_HEARTBEAT_URL in your environment

Step 6: Monitor SSL termination and TLS health

If you run Plug.Cowboy with HTTPS directly (without a reverse proxy), add a separate SSL endpoint monitor:

# lib/my_app/application.ex — HTTPS listener
{Plug.Cowboy, scheme: :https, plug: MyApp.Router, options: [
  port: 4443,
  certfile: "/etc/ssl/certs/cert.pem",
  keyfile: "/etc/ssl/private/key.pem",
  num_acceptors: 100
]}

In Vigilmon, add a second monitor for https://yourdomain.com:4443/health with SSL certificate expiry alerts enabled. Vigilmon will warn you 30, 14, and 7 days before your certificate expires.


Step 7: Status page

  1. Go to Status Pages → New Status Page in Vigilmon
  2. Add your HTTP monitor and heartbeat monitors
  3. Copy the public URL and share it in your README

README badge:

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

What you've built

| What | How | |------|-----| | Health check plug | MyApp.Plugs.Health with Ranch acceptor checks | | Acceptor pool monitoring | :ranch.info/1 connection and acceptor counts | | HTTP uptime monitoring | Vigilmon HTTP monitor → /health | | Multi-region probes | Vigilmon multi-probe checks | | Slack downtime alerts | Vigilmon Slack notification channel | | Background worker monitoring | Heartbeat ping on each successful tick | | SSL certificate alerts | Vigilmon HTTPS monitor with expiry warnings | | Status page | Vigilmon public status page |

Cowboy keeps your connections alive. Vigilmon keeps external eyes on whether those connections are actually being accepted.


Next steps

  • Add :ranch.info/1 metrics to your health endpoint for richer diagnostic data on acceptor pool saturation
  • Set response time thresholds in Vigilmon to catch slow middleware before users notice
  • Add heartbeat monitors for every background process that your application depends on for correctness
  • Use Vigilmon's response time history to correlate latency spikes with deployment events

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 →