basecamp/kamal · error · Kamal::ConfigurationError
SSL is only supported on a single server unless you provide
Error message
SSL is only supported on a single server unless you provide custom certificates, found #{hosts.size} servers for role #{name} What it means
Kamal raises Kamal::ConfigurationError while loading config when a role that runs the proxy (a primary role without proxy: false) has SSL enabled and spans more than one host, and no custom certificate is configured. Kamal's built-in SSL uses kamal-proxy's automatic certificates, which only work for a single server per role, so multi-host roles must bring their own certificate material.
Source
Thrown at lib/kamal/configuration/role.rb:162
host_path: asset_volume_directory(version), container_path: asset_path, options: asset_path_options
end
end
def asset_path_options
asset_path_config&.dig(1)
end
def asset_extracted_directory(version = config.version)
File.join config.assets_directory, "extracted", [ name, version ].join("-")
end
def asset_volume_directory(version = config.version)
File.join config.assets_directory, "volumes", [ name, version ].join("-")
end
def ensure_one_host_for_ssl
if running_proxy? && proxy.ssl? && hosts.size > 1 && !proxy.custom_ssl_certificate?
raise Kamal::ConfigurationError, "SSL is only supported on a single server unless you provide custom certificates, found #{hosts.size} servers for role #{name}"
end
end
private
def initialize_specialized_proxy
proxy_specializations = specializations["proxy"]
if primary?
# only false means no proxy for non-primary roles
@running_proxy = proxy_specializations != false
else
# false and nil both mean no proxy for non-primary roles
@running_proxy = !!proxy_specializations
end
if running_proxy?
proxy_config = proxy_specializations == true || proxy_specializations.nil? ? {} : proxy_specializations
View on GitHub (pinned to eee0083b38)
Solutions
- Provide custom certificates: add ssl_certificate: /path/cert.pem and ssl_private_key: /path/key.pem under proxy: in deploy.yml (custom_ssl_certificate? then passes)
- Reduce the role to a single host (hosts: [ one.ip ]) and keep automatic SSL
- Terminate SSL upstream (ALB/CloudFlare/CDN) and set proxy: ssl: false
- Split traffic across multiple single-host roles (e.g. web and web2), each with its own automatic cert
Example fix
# deploy.yml (before)
web:
hosts:
- 1.1.1.1
- 2.2.2.2
proxy:
ssl: true
# deploy.yml (after)
web:
hosts:
- 1.1.1.1
- 2.2.2.2
proxy:
ssl: true
ssl_certificate: /etc/ssl/myapp.crt
ssl_private_key: /etc/ssl/myapp.key Defensive patterns
Strategy: validation
Validate before calling
# Pre-flight before deploying: fail fast with your own message
require "yaml"
def ssl_multi_host?(deploy_yml = "config/deploy.yml")
config = YAML.load_file(deploy_yml, aliases: true)
proxy = config.dig("proxy") || {}
return false unless proxy["ssl"]
return false if proxy["ssl_certificate"] && proxy["ssl_private_key"]
config.fetch("servers", {}).any? do |_role, spec|
hosts = spec.is_a?(Hash) ? spec["hosts"] : spec
Array(hosts).size > 1
end
end
abort "multi-host role with automatic SSL will fail kamal config" if ssl_multi_host? Prevention
- Decide the TLS strategy before scaling past one host: custom certs or terminate SSL upstream
- Keep certificate/key paths in deploy.yml under proxy: so custom_ssl_certificate? stays true when hosts grow
- Run kamal config in CI to catch this at pull-request time, not deploy time
When it happens
Trigger: deploy.yml with servers: web: hosts: [ip1, ip2] (hosts.size > 1) on the primary role together with proxy: ssl: true (and no ssl: false override) and no proxy.ssl_certificate / proxy.ssl_private_key. Raised from ensure_one_host_for_ssl during Kamal::Configuration load, so any kamal command that builds the config fails.
Common situations: Scaling a web role from one server to several while relying on Kamal's automatic Let's Encrypt certs; copying a single-host example config and adding hosts; upgrading Kamal versions where certificate option names changed; forgetting that only the primary role runs the proxy.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- Different roles can't share the same host for SSL: #{duplica
- Conflicting proxy run configurations for host #{host}
- Secret '#{key}' not found in #{secrets_files.join(", ")}
- Failed to get endpoint for #{role} on #{host}, did the conta
- Failed to get endpoint for #{role} on #{host}, did the conta
AI-assisted analysis of basecamp/kamal@eee0083b38 (2026-08-21).
Data as JSON: /api/errors/eb6e44eaa2b2aff7.
Report an issue: GitHub.