Is it possible to start a Phoenix Server manually?

I know that you can tell the phoenix server to start manually? I know you can start the server automatically with mix phx.start or by setting server: true in your Endpoint configuration, but I don’t see a way to start it manually after the Endpoint is already started.

My use-case is that if a given port is already taken, I want to start on the next available port. I suppose one workaround is to set server: true but then don’t start the endpoint in the initial supervision tree and use a DynamicSupervisor or similar to start the endpoint manually. Tried this and it doesn’t work :cry: If I change the port than the server doesn’t start. Is this just unsupported in Phoenix?


It looks like I’ll have to use the init/2 callback on an Endpoint:

Although I’m not sure how I’ll handle the restart and increment logic with that yet


Okay, here is what I ended up with:

Instead of adding MyApp.Endpoint directly to my supervision tree I am adding this LsppWeb.PhoenixPortSupervisor that uses DynamicSupervisor to start up my Phoenix Endpoint, incrementing the port if it fails to startup initially.

defmodule LsppWeb.PhoenixPortSupervisor do
  @moduledoc """
  Starts up Phoenix, but controls the port with application configuration,
  increments the port until Phoenix starts up successfully.
  use GenServer

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

  def init(_) do
    initial_port = initial_port()

    start_phoenix(initial_port, initial_port)

  defp start_phoenix(initial_port, port) when port - initial_port < 15 do

    DynamicSupervisor.start_child(LsppWeb.DynamicSupervisor, LsppWebWeb.Endpoint)
    |> interpret_results()
    |> case do
      {:ok, _} -> {:ok, []}
      {:error, :eaddrinuse} -> start_phoenix(initial_port, port + 1)

  defp start_phoenix(_, _) do
    {:error, :ports_exhausted}

  def get_port, do: Application.get_env(:lspp_web, :phoenix_port)

  defp set_port(port) do
    Application.put_env(:lspp_web, :phoenix_port, port)

  defp interpret_results({:ok, pid}), do: {:ok, pid}

  defp interpret_results({:error, error}) do
    case error do
       {:failed_to_start_child, {:ranch_listener_sup, LsppWebWeb.Endpoint.HTTP},
         {:failed_to_start_child, :ranch_acceptors_sup,
          {:listen_error, LsppWebWeb.Endpoint.HTTP, :eaddrinuse}}}}} ->
        {:error, :eaddrinuse}

  defp initial_port do
    cond do
      port = System.get_env("PORT") ->

      config = Application.get_env(:lspp_web, LsppWebWeb.Endpoint) ->
        get_in(config, [:http, :port])

      true ->
        raise "Port not set"

Then in LsppWeb.Endpoint I define an init/2 callback:

  def init(supervisor, config) do
    port = LsppWeb.PhoenixPortSupervisor.get_port()
    config = put_in(config[:http], [:inet6, {:port, port}])

    {:ok, config}

Any feedback or critique is welcome. The ugliest part of the code right now is how I shuttle the new port through the application environment. I suppose since LsppWeb.PhoenixPortSupervisor is a GenServer maybe that could be handled with a, but then I would need another GenServer/process since currently LsppWeb.PhoenixPortSupervisor doesn’t finish it’s startup until my LsppWeb.Endpoint has finished.



Might be not what you want, but just in case, you can try setting the port to 0 in your config, then cowboy would ask ranch would ask gen tcp would ask OS for a random available port (Erlang -- gen_tcp).

If Port == 0, the underlying OS assigns an available port number, useinet:port/1 to retrieve it.

You can then fetch the port by using something like :ranch.get_port(YourAppWeb.Endpoint.HTTP). If you are not sure what module name plug decided to use for you cowboy server, try runing :ets.i(:ranch_server) in iex.


Ah that is interesting, didn’t know about that option. But for this case I’d prefer to know what ports the server would possibly be running on.

You can probably also use :gen_tcp.listen/2 to check if the port is taken, and only after that start the phoenix app. It would probably simplify the logic a bit (no need for dynamic supervisors, just a function that runs before the application starts).

def find_port(port) do
  case :gen_tcp.listen(port, []) do
    {:ok, socket} ->
    {:error, :eaddrinuse} ->
      find_port(port + 1)

Hmm, that does seem like it would clean up a bunch. Especially my crazy error checking pattern match. I’ll ty it out :+1: