{"record":{"id":"6fafd02d6012485b","repo":"basecamp/kamal","slug":"different-roles-can-t-share-the-same-host-for-ssl","errorCode":null,"errorMessage":"Different roles can't share the same host for SSL: #{duplicates.join(\", \")}","messagePattern":"Different roles can't share the same host for SSL: #(.+?)","errorType":"validation","errorClass":"Kamal::ConfigurationError","httpStatus":null,"severity":"error","filePath":"lib/kamal/configuration.rb","lineNumber":403,"sourceCode":"\n      if hooks.any?\n        raise Kamal::ConfigurationError, \"Found #{hooks.join(\", \")}, these should be renamed to (pre|post)-proxy-reboot\"\n      end\n\n      true\n    end\n\n    def ensure_one_host_for_ssl_roles\n      roles.each(&:ensure_one_host_for_ssl)\n\n      true\n    end\n\n    def ensure_unique_hosts_for_ssl_roles\n      hosts = roles.select(&:ssl?).flat_map { |role| role.proxy.hosts }\n      duplicates = hosts.tally.filter_map { |host, count| host if count > 1 }\n\n      raise Kamal::ConfigurationError, \"Different roles can't share the same host for SSL: #{duplicates.join(\", \")}\" if duplicates.any?\n\n      true\n    end\n\n    def ensure_local_registry_remote_builder_has_ssh_url\n      if registry.local? && builder.remote?\n        unless URI(builder.remote).scheme == \"ssh\"\n          raise Kamal::ConfigurationError, \"Local registry with remote builder requires an SSH URL (e.g., ssh://user@host)\"\n        end\n      end\n\n      true\n    end\n\n    def ensure_no_conflicting_proxy_runs\n      all_hosts.each do |host|\n        run_configs = proxy_runs(host)\n        if run_configs.uniq.size > 1","sourceCodeStart":385,"sourceCodeEnd":421,"githubUrl":"https://github.com/basecamp/kamal/blob/eee0083b38661c3707c6b6052cc89e85038a096c/lib/kamal/configuration.rb#L385-L421","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Restructure so only one ssl role includes each host — typically make the primary `web` role the only ssl role on that machine.","If two roles must share a host, drop `ssl: true` from the secondary role and terminate SSL once at the primary role's proxy.","Alternatively split the roles onto distinct hosts so the host lists no longer overlap."],"exampleFix":"# config/deploy.yml — before\nservers:\n  web:\n    hosts: [ \"1.2.3.4\" ]\n    ssl: true\n  admin:\n    hosts: [ \"1.2.3.4\" ]\n    ssl: true\n\n# after\nservers:\n  web:\n    hosts: [ \"1.2.3.4\" ]\n    ssl: true\n  admin:\n    hosts: [ \"1.2.3.4\" ]","handlingStrategy":"validation","validationCode":"def ssl_hosts_unique?(raw)\n  hosts = raw[\"servers\"].to_a.filter_map do |_name, entry|\n    next unless entry.is_a?(Hash) && entry[\"ssl\"]\n    Array(entry[\"hosts\"]).map { |h| h.is_a?(Hash) ? h.keys.first : h }\n  end.flatten\n  hosts.tally.values.all? { |c| c == 1 }\nend","typeGuard":null,"tryCatchPattern":"begin\n  config = Kamal::Configuration.new(create_config_files: false)\nrescue Kamal::ConfigurationError => e\n  puts \"Deploy config invalid: #{e.message}\"\n  exit 1\nend","preventionTips":["Designate a single ssl-terminating role (usually web) per host.","Before enabling ssl on a role, list which machines that role shares with other ssl roles.","When consolidating servers, re-audit ssl flags on every role touching the shared host."],"tags":["kamal","ssl","proxy","roles","configuration"],"backgroundTag":"conflicting-config-entries","analyzedSha":"eee0083b38661c3707c6b6052cc89e85038a096c","analyzedAt":"2026-08-21T15:17:22.045Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}