How to configure Let´s Encrypt certificate without Nginx?

I am trying to make my application work without nginx, until everything is normal, but adding certbot I am having these problems. Elixir 1.9 and Phoenix 1.4, deploy mix releases without distillery.

While running the command to get the certificate I had no problems and also gave these permissions:

mkdir -p /root/certbot/.well-known/acme-challenge
sudo chown -R root:root /root/certbot/.well-known/acme-challenge

Error:

Running StudiocodeWeb.Endpoint with cowboy 2.6.3 at 0.0.0.0:80 (http)
Failed to start Ranch listener StudiocodeWeb.Endpoint.HTTPS in :ranch_ssl:listen([cacerts: :..., key: :..., cert: :..., alpn_preferred_protocolsout 08 20:51:08 studiocode Application studiocode exited: Studiocode.Application.start(:normal, []) returned an error: shutdown: failed to start child: StudiocodeWeb.Endpoiout 08 20:51:08 studiocode studiocode[930]:     ** (EXIT) shutdown: failed to start child: {:ranch_listener_sup, StudiocodeWeb.Endpoint.HTTPS}
         ** (EXIT) shutdown: failed to start child: :ranch_acceptors_sup
             ** (EXIT) {:listen_error, StudiocodeWeb.Endpoint.HTTPS, {:options, {:certfile, nil}}}
{"Kernel pid terminated",application_controller,"{application_start_failure,studiocode,{{shutdown,{failed_to_start_child,'Elixir.StudiocodeWeb.Endpoint',{shutdown,{fout 08 20:51:09 studiocode studiocode[930]: Kernel pid terminated (application_controller) ({application_start_failure,studiocode,{{shutdown,{failed_to_start_child,'Elixir.StudiocodeWeb.Endpoint',{shutdown,{faout 08 20:51:09 studiocode studiocode[930]: [1B blob data]

My config/prod.ex:

config :studiocode, StudiocodeWeb.Endpoint,
  load_from_system_env: true,
  url: [host: System.get_env("EXTERNAL_HOSTNAME") || "localhost", port: 443, scheme: "https"], #port: 443, scheme: "https"
  http: [port: 80],
  force_ssl: [hsts: true],
  https: [
    port: 443,
    cipher_suite: :strong,
    opt_app: :studiocode,
    keyfile: System.get_env("SSL_KEY_FILE"),
    certfile: System.get_env("SSL_CERT_FILE"),
    cacertfile: System.get_env("SSL_CACERT_FILE")
  ],
  server: true,
  cache_static_manifest: "priv/static/cache_manifest.json",
  code_reloader: false,
  secret_key_base: System.get_env("SECRET_KEY_BASE")

Service:

[Unit]
Description=Phoenix server for DeployPhoenix app
After=network.target

[Service]
User=root
Group=root
Restart=on-failure
RestartSec=5
Environment=REPLACE_OS_VARS=true
Environment=PORT=80
Environment=EXTERNAL_HOSTNAME=studiocode.com.br
Environment=SECRET_KEY_BASE=...
Environment=DATABASE_URL=ecto...

Environment=ACME_CHALLENGE_DIR=/root/certbot
Environment=SSL_CERT_FILE=/etc/letsencrypt/live/studiocode.com.br/cert.pem
Environment=SSL_CACERT_FILE=/etc/letsencrypt/live/studiocode.com.br/chain.pem
Environment=SSL_KEY_FILE=/etc/letsencrypt/live/studiocode.com.br/privkey.pem

WorkingDirectory=/home/phoenix-studiocode
ExecStart=/home/phoenix-studiocode/_build/prod/rel/studiocode/bin/studiocode start
ExecStop=/home/phoenix-studiocode/_build/prod/rel/studiocode/bin/studiocode stop

[Install]
WantedBy=multi-user.target

Config vi /etc/letsencrypt/letsencrypt.ini:

Install: sudo apt-get install -y software-properties-common && sudo add-apt-repository -y ppa:certbot/certbot && sudo apt-get update && sudo apt-get install -y certbot

Get: echo “A”| certbot certonly --config /etc/letsencrypt/letsencrypt.ini

rsa-key-size = 4096
email =myemail@gmail.com
domains = studiocode.com.br, www.studiocode.com.br
text = True
authenticator = webroot
preferred-challenges = http-01
webroot-path = /root/certbot/

Route:

#Certbot Letsencrypt
scope "/.well-known/acme-challenge", StudiocodeWeb do
  get "/:challenge", PageController, :certbot
end

Controller:

def certbot(conn, %{"challenge" => file_name}) do
    base_path = System.get_env("ACME_CHALLENGE_DIR")
    safe_file_name = Path.basename(file_name)

    case File.read(Path.join([base_path, "/.well-known/acme-challenge", safe_file_name])) do
      {:ok, content} ->
        send_resp(conn, 200, content)
      _ ->
        send_resp(conn, 200, "Not Valid")
    end
  end

  def certbot(conn, _) do
    send_resp(conn, 200, "Not valid")
  end

System.get_env in prod.exs runs at compile time not runtime. You want to either turn those into "${EXTERNAL_HOSTNAME}" or use Elixir 1.9 releases and have a releases.exs file that gets run at start time.

I removed System.get_env (“EXTERNAL_HOSTNAME”) and left it as “studiocode.com.br” and still the same error.

It isn’t about that specific one, it’s all of them:

  https: [
    port: 443,
    cipher_suite: :strong,
    opt_app: :studiocode,
    keyfile: System.get_env("SSL_KEY_FILE"),
    certfile: System.get_env("SSL_CERT_FILE"),
    cacertfile: System.get_env("SSL_CACERT_FILE")
  ],
secret_key_base: System.get_env("SECRET_KEY_BASE")

etc

1 Like

It worked :). Do you recommend leaving this config in prod.exs or releases.exs ?

If you’re using Elixir 1.9 releases then I’d move it to releases.exs which was explicitly built for this purpose. If you do so you can return to doing nice System.get_env calls.

4 Likes

Thank you!

1 Like