tutorial

How to Monitor Mint with Vigilmon

Mint is Elixir's low-level HTTP/1 and HTTP/2 client — powerful but process-managed, making connection failures hard to surface. Here's how to monitor Mint-backed services with Vigilmon.

How to Monitor Mint with Vigilmon

Mint is Elixir's low-level HTTP client, designed for building higher-level HTTP clients rather than calling APIs directly. It gives you full control over HTTP/1 and HTTP/2 connections — but that control comes with responsibility. Unlike higher-level clients, Mint connections are process-owned and not supervised by default, which means connection failures can leave your application quietly degraded.

This tutorial sets up layered monitoring for Mint-backed services:

  • A health endpoint that verifies Mint can establish connections
  • HTTP uptime monitoring with Vigilmon
  • Connection pool health via heartbeats
  • HTTP/2 multiplexing health checks
  • Alerts when connection establishment fails

Why Monitor Mint?

| Signal | What it catches | |---|---| | Connection establishment | TCP/TLS handshake failures to upstream hosts | | HTTP/2 settings negotiation | Protocol negotiation failures during ALPN | | Response streaming | Incomplete response bodies from slow upstreams | | Connection pool exhaustion | All connections busy, new requests queuing | | TLS certificate issues | Expired or mismatched upstream certificates |

Mint is often used as the backend for Finch and other pooled clients. Failures at this layer affect every service that depends on those pools.


Step 1: Create a Mint Connection Health Check

Mint connections are process-owned, so a health check needs to open a real connection and make a request:

# lib/my_app/health/mint_health.ex
defmodule MyApp.Health.MintHealth do
  @moduledoc """
  Tests Mint HTTP/1 and HTTP/2 connection establishment to configured upstream hosts.
  """

  @timeout 5_000

  def check_upstream(host, port \\ 443, scheme \\ :https) do
    with {:ok, conn} <- Mint.HTTP.connect(scheme, host, port, []),
         {:ok, conn, request_ref} <- Mint.HTTP.request(conn, "GET", "/", [], nil),
         {:ok, _conn, response} <- receive_response(conn, request_ref) do
      status = get_status(response)
      {:ok, %{host: host, status: status, http_version: conn.http_version}}
    else
      {:error, reason} ->
        {:error, %{host: host, reason: inspect(reason)}}

      {:error, conn, reason, _responses} ->
        Mint.HTTP.close(conn)
        {:error, %{host: host, reason: inspect(reason)}}
    end
  end

  defp receive_response(conn, request_ref) do
    receive do
      message ->
        case Mint.HTTP.stream(conn, message) do
          {:ok, conn, responses} ->
            if done?(responses, request_ref) do
              {:ok, conn, responses}
            else
              receive_response(conn, request_ref)
            end

          {:error, conn, reason, _responses} ->
            {:error, conn, reason, []}
        end
    after
      @timeout -> {:error, conn, :timeout, []}
    end
  end

  defp done?(responses, ref) do
    Enum.any?(responses, fn
      {:done, ^ref} -> true
      _ -> false
    end)
  end

  defp get_status(responses) do
    case Enum.find(responses, &match?({:status, _, _}, &1)) do
      {:status, _ref, status} -> status
      nil -> nil
    end
  end
end

Add a controller endpoint:

# lib/my_app_web/controllers/health_controller.ex
def mint(conn, _params) do
  upstreams = Application.get_env(:my_app, :monitored_upstreams, [
    {"api.stripe.com", 443, :https},
    {"api.sendgrid.com", 443, :https}
  ])

  results = Enum.map(upstreams, fn {host, port, scheme} ->
    MyApp.Health.MintHealth.check_upstream(host, port, scheme)
  end)

  failed = Enum.filter(results, &match?({:error, _}, &1))

  if Enum.empty?(failed) do
    json(conn, %{status: "ok", checked: length(results)})
  else
    conn
    |> put_status(503)
    |> json(%{status: "degraded", failures: Enum.map(failed, fn {:error, r} -> r end)})
  end
end

Step 2: Monitor HTTP/2 Multiplexing Health

HTTP/2 allows multiple requests over a single connection. Add a check that verifies H2 negotiation succeeds:

# lib/my_app/health/mint_http2_health.ex
defmodule MyApp.Health.MintHTTP2Health do
  def check_h2(host, port \\ 443) do
    opts = [transport_opts: [alpn_advertised_protocols: ["h2", "http/1.1"]]]

    case Mint.HTTP.connect(:https, host, port, opts) do
      {:ok, conn} ->
        version = conn.http_version
        Mint.HTTP.close(conn)
        {:ok, %{host: host, negotiated: version, http2: version == :"HTTP/2"}}

      {:error, reason} ->
        {:error, %{host: host, reason: inspect(reason)}}
    end
  end
end

This catches scenarios where a host drops HTTP/2 support (forcing all requests to HTTP/1.1 and reducing throughput significantly).


Step 3: Add Vigilmon Uptime Monitoring

  1. Log in to vigilmon.online and click Add Monitor.
  2. Set Type to HTTP / HTTPS.
  3. Enter your health endpoint: https://yourapp.com/health/mint.
  4. Set Check interval to 1 minute.
  5. Set Expected HTTP status to 200.
  6. Under Advanced, add a Response body contains check: "status":"ok".
  7. Click Save.

Vigilmon now verifies your Mint connection pool can reach its upstreams every minute.


Step 4: Add a Heartbeat for Finch-based Connection Pools

Mint underpins Finch, which manages pooled connections. Monitor pool health with a heartbeat:

# lib/my_app/workers/finch_health_worker.ex
defmodule MyApp.Workers.FinchHealthWorker do
  use GenServer

  @interval :timer.minutes(1)
  @vigilmon_heartbeat_url "https://vigilmon.online/api/heartbeat/YOUR_HEARTBEAT_ID"
  @upstream_url "https://api.example.com/health"

  def start_link(_), do: GenServer.start_link(__MODULE__, nil, name: __MODULE__)

  def init(_) do
    schedule()
    {:ok, nil}
  end

  def handle_info(:check, state) do
    case check_pool_health() do
      :ok -> ping_vigilmon()
      {:error, reason} ->
        require Logger
        Logger.warning("Mint/Finch pool health check failed: #{inspect(reason)}")
    end

    schedule()
    {:noreply, state}
  end

  defp check_pool_health do
    request = Finch.build(:get, @upstream_url)

    case Finch.request(request, MyApp.Finch, receive_timeout: 5_000) do
      {:ok, %Finch.Response{status: status}} when status in 200..299 -> :ok
      {:ok, %Finch.Response{status: status}} -> {:error, {:unexpected_status, status}}
      {:error, reason} -> {:error, reason}
    end
  end

  defp ping_vigilmon do
    request = Finch.build(:get, @vigilmon_heartbeat_url)
    Finch.request(request, MyApp.Finch)
  end

  defp schedule, do: Process.send_after(self(), :check, @interval)
end

Add it to your supervision tree in application.ex:

children = [
  {Finch, name: MyApp.Finch},
  MyApp.Workers.FinchHealthWorker,
  # ...
]

To create the heartbeat in Vigilmon:

  1. Click Add MonitorCron / Heartbeat.
  2. Set the expected ping interval to 1 minute.
  3. Copy the generated heartbeat URL into @vigilmon_heartbeat_url.

Step 5: Set Up Alerts

  1. Go to Alert ChannelsAdd Channel.
  2. Choose Slack, Email, or PagerDuty.
  3. Set alert thresholds: 2 consecutive failures before alerting.
  4. Assign the channel to your Mint HTTP monitor and heartbeat.

For services where HTTP/2 is critical for throughput (high-concurrency APIs), add a separate monitor for the H2 health endpoint and set a tighter alert threshold.


Key Metrics to Watch

| Metric | Vigilmon feature | What to alert on | |---|---|---| | Upstream connectivity | HTTP monitor on /health/mint | Any 5xx or timeout | | Connection pool health | Heartbeat monitor | Missing ping for > 2 min | | Response time | Response time chart | P95 > 2s over 5 minutes | | SSL certificate | Cert expiry monitor | Expires in < 14 days | | HTTP/2 negotiation | HTTP monitor on H2 endpoint | Non-200 response |


Conclusion

Mint's low-level design gives Elixir developers precise control over HTTP connections, but that control requires explicit monitoring. A health endpoint that opens real Mint connections, a Finch pool heartbeat, and Vigilmon watching both gives you early warning before connection failures cascade into application errors.

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 →