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:
- Sign up at vigilmon.online
- Click New Monitor → HTTP
- Enter
https://yourdomain.com/health - Set check interval: 1 minute (paid) or 5 minutes (free)
- Under Advanced, set the expected response body to contain
"ok"and response time threshold to 2000 ms - 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:
- Click New Monitor → Heartbeat
- Set the expected interval (e.g. 35 minutes for a 30-minute worker, giving a 5-minute grace period)
- Copy the unique ping URL
- Set
VIGILMON_CLEANUP_HEARTBEAT_URLin 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
- Go to Status Pages → New Status Page in Vigilmon
- Add your HTTP monitor and heartbeat monitors
- Copy the public URL and share it in your README
README badge:

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/1metrics 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.