Samuel Willis

Fly.io, LiveView, and custom domains

How to configure a Fly.io deployed Phoenix LiveView application with a custom domain

Fly.io + Custom Domains + LiveView configurations

Getting a basic Phoenix application deployed on Fly.io is dead simple if you follow Elixir getting started guide.

However, if you end up configuring a custom domain for your application, the configuration for the application prevents Websocket connections when you visit the application at the custom domain.

Thankfully resolving this is dead simple and there’s several approaches you can take!

Option 1: Update fly.toml

The default configuration for a Fly.io deployed application uses the PHX_HOST env variable to build the url for the application’s Endpoint. Combined with the Endpoint defaulting to check_origin: true it results in all connections with an origin differing from the configured url to be blocked.

Changing the PHX_HOST value in your fly.toml to be the custom domain and deploying the change will allow connections from the custom domain. This will prevent any other domains from connecting successfully, including the Fly provisioned domain, but is simple and quick.

Option 2: Adjust the Endpoint’s check_origin configuration

Adjusting the Endpoint’s check_origin will allow specifying a list of domains that will be permitted to make websocket connections.

As per the docs the check_origin can be set to a list of explicitly allowed origins.

So doing an update like so will allow both the Fly provisioned url and the custom domain.

# config/runtime.exs
host = System.get_env("PHX_HOST") || "example.com"
port = String.to_integer(System.get_env("PORT") || "443")

config :your_application, YourApplicationWeb.Endpoint,
    check_origin: [
        # Allow the Fly provisioned url to connect
        "https://#{host}:#{port}",
        # Allow your custom domain to connect at any subdomain
        # or at the apex.
        "//yourcustomdomain.com:#{port}",
    ],
    url: [host: host, port: port, scheme: "https"],
    # ...

This is a tidy solution that explicitly defines the allowed URLs but requires manual updates any time you wish to provision a new domain.

Option 3: Allow any origins that match the current request’s host

A third option allows for the addition of any custom domains without needing configuration or code changes.

This requires adjusting the Endpoint’s configuration check_origin as well as the force_ssl option.

Setting the Endpoint’s check_origin: :conn will accept any origin matching the request connection’s host, port, and scheme.

This can be combined with the force_ssl option to ensure the correct handling of Fly provisioned domains as well as custom domains (and ensure SSL is used as an added bonus).

Similar to option 2, the config/runtime.exs needs updating and, since the force_ssl is a compile time option, the config/prod.exs. There’s a couple settings to update:

Putting this all together results in the following:

# config/runtime.exs
host = System.get_env("PHX_HOST") || "example.com"
port = String.to_integer(System.get_env("PORT") || "443")

config :your_application, YourApplicationWeb.Endpoint,
    check_origin: :conn,
    url: [host: host, port: port, scheme: "https"],
    # ...
# config/prod.exs
config :your_application, YourApplicationWeb.Endpoint,
  force_ssl: [
    hsts: true,
    host: nil,
    rewrite_on: [:x_forwarded_host, :x_forwarded_port, :x_forwarded_proto]
  ],
  # ...

Summary

Deploying Phoenix applications on Fly.io is a breeze but there are certain things, like adding a custom domain, that can cause small issues when deployed with the default configuration.

Hopefully this article illustrates how to get websocket connections working with a custom domain on Fly.io.

If you have alternatives, or adjustments, or any thoughts at all, feel free to open an issue in this website’s repo.

2024-08-16