basecamp/kamal · error · Kamal::ConfigurationError

Different roles can't share the same host for SSL: #{duplica

Error message

Different roles can't share the same host for SSL: #{duplicates.join(", ")}

What it means

Kamal::Configuration#ensure_unique_hosts_for_ssl_roles collects the proxy hosts of every role with `ssl: true`, tallies them, and raises if any host appears more than once. When a host runs the proxy for multiple SSL roles, the roles would fight over certificates and proxy configuration, so Kamal requires each SSL-enabled role's proxy hosts to be unique across roles.

Source

Thrown at lib/kamal/configuration.rb:403

      if hooks.any?
        raise Kamal::ConfigurationError, "Found #{hooks.join(", ")}, these should be renamed to (pre|post)-proxy-reboot"
      end

      true
    end

    def ensure_one_host_for_ssl_roles
      roles.each(&:ensure_one_host_for_ssl)

      true
    end

    def ensure_unique_hosts_for_ssl_roles
      hosts = roles.select(&:ssl?).flat_map { |role| role.proxy.hosts }
      duplicates = hosts.tally.filter_map { |host, count| host if count > 1 }

      raise Kamal::ConfigurationError, "Different roles can't share the same host for SSL: #{duplicates.join(", ")}" if duplicates.any?

      true
    end

    def ensure_local_registry_remote_builder_has_ssh_url
      if registry.local? && builder.remote?
        unless URI(builder.remote).scheme == "ssh"
          raise Kamal::ConfigurationError, "Local registry with remote builder requires an SSH URL (e.g., ssh://user@host)"
        end
      end

      true
    end

    def ensure_no_conflicting_proxy_runs
      all_hosts.each do |host|
        run_configs = proxy_runs(host)
        if run_configs.uniq.size > 1

View on GitHub (pinned to eee0083b38)

Solutions

  1. Restructure so only one ssl role includes each host — typically make the primary `web` role the only ssl role on that machine.
  2. If two roles must share a host, drop `ssl: true` from the secondary role and terminate SSL once at the primary role's proxy.
  3. Alternatively split the roles onto distinct hosts so the host lists no longer overlap.

Example fix

# config/deploy.yml — before
servers:
  web:
    hosts: [ "1.2.3.4" ]
    ssl: true
  admin:
    hosts: [ "1.2.3.4" ]
    ssl: true

# after
servers:
  web:
    hosts: [ "1.2.3.4" ]
    ssl: true
  admin:
    hosts: [ "1.2.3.4" ]
Defensive patterns

Strategy: validation

Validate before calling

def ssl_hosts_unique?(raw)
  hosts = raw["servers"].to_a.filter_map do |_name, entry|
    next unless entry.is_a?(Hash) && entry["ssl"]
    Array(entry["hosts"]).map { |h| h.is_a?(Hash) ? h.keys.first : h }
  end.flatten
  hosts.tally.values.all? { |c| c == 1 }
end

Try / catch

begin
  config = Kamal::Configuration.new(create_config_files: false)
rescue Kamal::ConfigurationError => e
  puts "Deploy config invalid: #{e.message}"
  exit 1
end

Prevention

When it happens

Trigger: Two roles (e.g. `web` and `admin`) both having hosts entries pointing at the same server while each sets `ssl: true`; adding `ssl: true` to a second role that shares a machine with the primary web role; host aliases resolving to the same literal host string in two ssl roles' lists.

Common situations: Consolidating what were separate servers onto one box for cost; copying role blocks and forgetting to prune hosts; enabling ssl on additional roles that share the proxy host.

Understand the failure class

Related errors


AI-assisted analysis of basecamp/kamal@eee0083b38 (2026-08-21). Data as JSON: /api/errors/6fafd02d6012485b. Report an issue: GitHub.