Health checks are an essential part of maintaining the uptime and stability of your web application. They allow monitoring systems, such as Kubernetes, Docker, or even simple external services, to periodically verify if your application is alive and functioning properly. In this post, we'll walk through how to add a basic health check plug to a Phoenix application.

What is a Plug?

In Elixir, a Plug is a module that implements two functions: init/1 and call/2. Plugs are a fundamental building block of Phoenix applications, allowing you to process requests before they reach your controller or return early responses when needed.

For this blog post, we'll create a simple plug that serves as a health check endpoint. The plug will return a 200 HTTP status code for any request that hits the /health path.

Step 1: Create the health check Plug

First, let's create the plug module. This plug will intercept requests to /health and return a 200 OK status, signaling that the application is healthy. If the request path is something else, the plug will simply pass the connection along the pipeline without modification.

Here's how the plug looks:

defmodule MyAppWeb.Plug.HealthCheck do
  import Plug.Conn

  # init/1 is required by the Plug behaviour but can be left as-is.
  def init(opts), do: opts

  # If the request path matches "/health", we return a 200 response.
  def call(conn = %Plug.Conn{request_path: "/health"}, _opts) do
    conn
    |> send_resp(200, "")
    |> halt()  # Halts further processing of the request.
  end

  # If the request path is anything else, we pass the connection along.
  def call(conn, _opts), do: conn
end

Here's how it works:

  • init/1: This function initializes the plug. In our case, it's a no-op, so it simply returns the options it receives.
  • call/2: The call function processes incoming requests. If the request's path is /health, we immediately respond with a 200 status and halt the request from proceeding further. For any other paths, the request continues to the next plug in the pipeline.

Step 2: Add the Plug to your endpoint

Next, we need to add the HealthCheck plug to the request pipeline. To do this, we include it in our application's endpoint module. This ensures that every incoming request passes through this plug before reaching any other parts of the application.

Here's how to include the plug in your Endpoint:

defmodule MyAppWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :my_app

  # ... omitted for brevity

  # Add the health check plug to the pipeline.
  plug MyAppWeb.Plug.HealthCheck

  # ... omitted for brevity
end

By adding plug MyAppWeb.Plug.HealthCheck, the health check route becomes available to your application.

Step 3: Test the health check

Once you've added the plug to your endpoint, start your application and visit /health in your browser or make a request using curl:

curl -I http://localhost:4000/health

You should receive a 200 OK response with an empty body. This means your health check plug is working as expected!

Why Use a health check?

Health checks are critical for several reasons:

  • Monitoring: Health checks allow external systems, like load balancers or orchestrators (e.g., Kubernetes), to monitor the health of your application.
  • Scaling: Systems that auto-scale based on the number of healthy instances rely on health checks to determine when to add or remove instances.
  • Failure detection: If an instance of your application stops responding, a health check can trigger a restart or another corrective action.

Conclusion

By adding this simple health check plug to your Phoenix application, you can ensure that external systems have an easy way to monitor its health. While this is a very basic check, you can easily extend it to include more complex checks, such as verifying the state of dependencies like databases or external services.

With this foundation, you're well on your way to making your Phoenix app more resilient and easier to monitor.