bg-tutorials

如何在HAProxy中配置SSL证书

HAProxy 是一款终止 TLS 的 TCP 和 HTTP 负载均衡器,它在边缘解密流量,并将纯 HTTP 转发给后端服务器。这使得它成为整个链路中唯一需要证书的机器,并且它要求证书采用特定的格式:一个文件,包含证书、任何中间证书以及私钥。

本指南涵盖整个流程:生成 CSR、组装 PEM 文件、编写前端和后端配置、检查配置,以及在不中断现有连接的情况下重新加载。所有命令均在 HAProxy 3.4 上运行,这是当前的长期支持分支,于 2026 年 6 月发布,维护至 2031 年第二季度。

HAProxy 需要什么:单一 PEM 文件

大多数服务器将证书、证书链和密钥作为三个独立的设置项。而 HAProxy 只需要一个。其配置手册将 crt 关键字描述为指定”一个包含所需证书和任何相关私钥的 PEM 文件”,该文件通过拼接 PEM 文件构建,并补充说”如果您的 CA 需要中间证书,也可以拼接到该文件中”。

在开始之前,有两个行为值得了解,因为它们能为您节省后续的工作量:

  • 密钥可以放在证书旁边,而不必放在证书内部。 如果文件中没有私钥,HAProxy 会查找同一路径下附加 .key 后缀的文件。因此 mydomain.pem 加上 mydomain.pem.key 与一个合并文件效果相同。
  • 您可以将 crt 指向一个目录。 HAProxy 会加载该目录下的每一个文件,并根据请求使用 SNI 选择合适的证书。这正是您如何用一个前端服务多个站点,而无需为每个站点单独写一条 bind 行。

生成 CSR 和私钥

CSR 是您提交给证书颁发机构的编码请求。在 HAProxy 机器上生成它,或者在任何可以安全保存私钥的地方生成,因为私钥永远不会离开您这边。

openssl req -new -newkey rsa:2048 -nodes 
  -keyout mydomain.key -out mydomain.csr 
  -subj "/C=US/ST=California/L=San Jose/O=Your Company/CN=mydomain.com" 
  -addext "subjectAltName=DNS:mydomain.com,DNS:www.mydomain.com"

-addext 这一行在实际操作中并非可选项。浏览器多年前就已停止将主机名与通用名称(Common Name)进行匹配,而只读取主题备用名称(Subject Alternative Name),因此仅包含 CN 的 CSR 生成的证书在所有当前浏览器中都会验证失败。请列出证书必须覆盖的每一个名称,包括裸域名和 www 形式(如果两者都提供服务)。

如果您希望针对每个字段单独输入提示,可以省略 -subj 和 -addext 选项。无论哪种方式,最终您都会得到两个文件:需要提交的 mydomain.csr,以及需要妥善保存的 mydomain.key。在提交之前,请通过我们的 CSR 解码器或在本地确认 CSR 中包含您预期的内容:

openssl req -noout -text -verify -in mydomain.csr

检查 SAN 条目是否已列出,以及签名是否通过验证。如果您根本不想使用命令行,我们的 CSR 生成器可以在浏览器中生成相同的一对文件。有关底层命令的更多背景信息,请参阅我们的 OpenSSL 命令指南。

获取证书签发

将 CSR 提交给 CA,选择与您要保护的对象相匹配的证书类型,并完成验证。单域证书覆盖一个主机名,通配符证书覆盖所有一级子域名,而多域名(SAN)证书则覆盖一系列不相关的名称。在负载均衡器的场景下,多域名证书是常见选择,因为一个 HAProxy 实例通常需要为多个站点提供服务。

CA 会返回一个包含您的证书和中间证书链的压缩包,通常以 CA 捆绑包文件的形式提供。两者都已经是 PEM 格式,这正是您所需要的。现在就应该为续期做好规划,而不要拖到以后:自 2026 年 3 月 15 日起,公开信任的 TLS 证书有效期最长不得超过 200 天,2027 年 3 月将降至 100 天,2029 年 3 月将降至 47 天,因此手动更换证书很快将变得不再切实可行。

构建 HAProxy 将读取的 PEM 文件

创建一个用于存放证书的目录,并在那里组装文件。从一开始就把所有内容集中放在一处;如果将其分散放在主目录、/etc/haproxy 和 /etc/ssl 之间,就很容易出现您编辑了一个文件而 HAProxy 却读取另一个文件的情况。

sudo mkdir -p /etc/haproxy/certs
sudo chmod 700 /etc/haproxy/certs

依次拼接证书、中间证书,然后是私钥。重定向操作必须以 root 身份运行,因此请通过管道输出给 tee,而不要写成 sudo cat ... > /etc/haproxy/certs/...,后者会因权限错误而失败,因为 shell 会以您自己的用户身份打开输出文件,早于 sudo 生效之前:

cat mydomain.crt intermediate.crt mydomain.key 
  | sudo tee /etc/haproxy/certs/mydomain.pem > /dev/null

该文件现在以明文形式包含您的私钥,因此在进一步操作之前请先锁定它:

sudo chown root:root /etc/haproxy/certs/mydomain.pem
sudo chmod 600 /etc/haproxy/certs/mydomain.pem

仅 root 权限在这里是正确的做法,而不是一种阻碍。HAProxy 启动时使用超级用户权限,其手册指出这是为了让它随后能够切换到自己的非特权用户,而它正是在启动期间读取证书。没有必要放宽权限让 haproxy 用户能够读取密钥,您也不应该这样做。

如果您在另一台机器上生成了 CSR,请先将文件复制过去,然后从传输目录中删除副本:

scp mydomain.crt intermediate.crt mydomain.key sysadmin@haproxy-server:/home/sysadmin/

配置 HAProxy

在服务器本身的终端编辑器中打开 /etc/haproxy/haproxy.cfg,比如 nano 或 vim。请就地编辑,而不要在工作站上编辑,这样您就不会重新加载一个与您测试过的文件不同的文件。

前端配置

一个前端可以同时接受纯 HTTP 和 HTTPS。为重定向绑定端口 80,为证书绑定端口 443,并将其余所有内容发送到后端:

frontend web_frontend
    mode http
    bind *:80
    bind *:443 ssl crt /etc/haproxy/certs/mydomain.pem alpn h2,http/1.1

    http-request redirect scheme https code 301 unless { ssl_fc }

    default_backend web_servers

alpn h2,http/1.1 提供 HTTP/2 支持,并回退至 HTTP/1.1。重定向只有在请求不是通过 TLS 到达时才会触发,这正是 ssl_fc 所测试的内容,因此端口 443 上的请求会直接通过。

将所有 TLS 选项放在同一条 bind 行上

这正是 HAProxy 配置中最常出错的地方,而且它是悄无声息地失败。许多指南通常将 TLS 加固作为第二步呈现,展示为端口 443 新增一条 bind 行并附加额外选项。如果您添加了这样一条新行,而不是编辑现有的那一行,最终您会为同一端口配置两条 bind 行,而 HAProxy 并不会报错。它会正常启动,并在端口 443 上打开两个独立的监听套接字,二者的 TLS 设置各不相同。哪个连接会落到哪个套接字上并不受您控制,因此您的加固措施大约只能覆盖一半的流量。

配置检查同样无法捕捉到这个问题,其官方文档也解释了原因:-c “仅对配置文件执行检查,并在尝试绑定之前退出”。重复监听器是一种绑定时才会出现的状况,因此语法检查永远无法发现它。

请改为设置全局默认值,这样文件中的每一条 bind 行都会继承这些设置,也就没有什么可以重复配置的了:

global
    ssl-default-bind-options ssl-min-ver TLSv1.2 prefer-client-ciphers
    ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256

在您将这段配置复制到任何地方之前,有一点值得了解:ssl-min-ver 按照 HAProxy 自身的说法,默认值本来就已经是 TLSv1.2。将其设置为 TLSv1.2 并不会改变任何行为,只是记录了这一意图,这没有问题,但它并不像人们常说的那样是一项安全改进。真正起作用的是两个密码套件设置,它们之所以分开是有意为之的:ssl-default-bind-ciphers 适用于 TLS 1.2 及以下版本,ssl-default-bind-ciphersuites 适用于 TLS 1.3。如果只设置前者,您的 TLS 1.3 套件仍将保持默认设置。

后端配置

为每台服务器指定一个真实地址。这一点看起来简单,实际上非常重要:

backend web_servers
    mode http
    balance roundrobin
    option httpchk GET /
    server web1 10.0.0.11:80 check
    server web2 10.0.0.12:80 check

像 server web1 :80 check 这样省略地址的服务器配置行不会引发错误。HAProxy 会接受它,并将缺失的地址解析为 0.0.0.0,其手册将其视为一个特殊值,意味着连接会被转发到与客户端连接的相同 IP 地址。而该地址正是 HAProxy 自身,因此后端会悄无声息地指向 HAProxy 自己的端口 80,也就是您刚配置的前端。流量会回环进入重定向,而不会到达任何应用程序,而健康检查看起来却是正常的,因为确实有东西在监听。如果负载均衡器对每一个 HTTPS 请求都以重定向到自身作为响应,请首先检查服务器配置行。

由于 HAProxy 终止了 TLS,后端接收到的是端口 80 上的纯 HTTP,因此不需要自己的证书。如果策略要求在这一环节也进行加密,请在服务器配置行中添加 ssl verify required 及一个 CA 文件,并将其指向端口 443。

将原始协议方案传递给后端。由于后端现在接收的是纯 HTTP,像 WordPress、Django 和 Rails 这样会生成绝对 URL 的应用程序,将会生成以 http:// 开头的 URL,这会表现为混合内容问题,或应用程序自身产生的重定向循环。在前端添加一行:

http-request set-header X-Forwarded-Proto https if { ssl_fc }

并在后端添加一行,这样也会传递真实的客户端 IP 地址:

option forwardfor

大多数框架随后还需要设置为信任这些请求头;这部分配置是在应用程序中完成的,而不是在 HAProxy 中。

应用配置之前先进行检查

切勿在未经验证的文件上重启负载均衡器。请先进行验证:

sudo haproxy -c -V -f /etc/haproxy/haproxy.cfg

加上 -V 参数后,成功时它会打印 Configuration file is valid 并返回退出状态零。如果不加该参数,成功时不会有任何输出。无论文件是否有效,任何警告都会被报告出来,因此请阅读输出内容,而不要仅凭没有出现红色文字就认为一切正常。

重新加载而非重启

sudo systemctl reload haproxy

在负载均衡器上,这一区别是实实在在的。重新加载会启动一个新进程,并向旧进程发出信号,让其”完成正在处理的工作后再退出”,因此已经在进行中的请求会正常完成。而重启会向旧进程发出信号,让其”立即终止,而不管是否完成正在处理的工作”,这会切断实时连接,包括上传和长时间运行的 API 调用。仅在重新加载无法应用相关变更时才使用重启,比如更改了全局部分的进程设置之后。

确认服务已恢复并在两个端口上都处于监听状态:

sudo systemctl status haproxy
sudo ss -tlnp | grep haproxy

这里您需要的是每个端口恰好一个监听套接字。如果端口 443 上出现两个,说明您遇到了上文所述的重复 bind 行问题。

验证证书是否正确提供

从服务器本身检查 HAProxy 实际提供的内容,包括证书链:

openssl s_client -connect mydomain.com:443 -servername mydomain.com < /dev/null

阅读输出顶部的 Certificate chain 部分。您的证书应出现在深度 0 处,中间证书应出现在深度 1 处。如果深度 1 缺失,说明中间证书未能被写入 PEM 文件,网站在某些浏览器中可以正常工作,而在另一些浏览器中会失败。在判断这一点时,请忽略 Verify return code 这一行:它仅报告证书链的验证结果,在与您所测试内容完全无关的情况下也可能显示为成功。

然后从外部进行确认,这样得到的结果才能反映真实访客所看到的情况。我们的 SSL 检测工具会报告证书、证书链以及到期日期。

续期与自动化

替换 PEM 文件就是整个续期流程:用新证书和相同或新的密钥重新构建该文件,然后重新加载。HAProxy 配置中没有任何内容引用到期日期,因此只要文件路径保持不变,就无需修改配置。

随着证书有效期不断缩短,现在就值得设置自动化流程。HAProxy 从 3.2 版本开始内置了 ACME 客户端,通过 acme 部分进行配置。目前请将其视为预览功能,而非生产环境基础设施:在 3.4 版本中它仍被标记为实验性功能,需要在全局部分启用 expose-experimental-directives,它仅支持 http-01、dns-01 和 dns-persist-01 这几种质询类型,并且它生成的证书必须从 stats socket 导出才能落盘。dns-persist-01 类型是在 3.4 版本中新增的,使用一条静态 TXT 记录,该记录一次设置后在各次续期之间都不会变化,因此每次续期都不需要对 DNS 提供商的 API 拥有写入权限。目前成熟稳妥的替代方案是运行一个外部 ACME 客户端,让其部署步骤重新构建 PEM 文件并重新加载 HAProxy,这与我们的 Apache 和 NGINX ACME 指南中描述的模式相同。

常见问题解答

证书、证书链和密钥在 PEM 文件中应按什么顺序排列?

先是您的证书,然后是任何中间证书,最后是私钥。HAProxy 的手册将该文件描述为通过拼接 PEM 文件构建而成,并说明中间证书可以拼接进去。如果您希望将密钥单独保存,可以完全省略它,并将其保存为同一路径附加 .key 后缀的文件,HAProxy 会自动加载它。

为什么我的 HTTPS 网站会陷入自我重定向的循环?

通常是因为后端服务器配置行没有指定地址,例如 server web1 :80 check。HAProxy 会将空地址解析为 0.0.0.0,而该地址意味着连接会被转发到与客户端连接的相同 IP 地址,也就是 HAProxy 自身。因此后端指向的是 HAProxy 自己的端口 80,也就是发出 HTTP 到 HTTPS 重定向的那个前端。请为每一条服务器配置行指定一个真实的 IP 地址或主机名。

同一前端中,端口 443 能否配置两条 bind 行?

可以,但这正是问题所在。HAProxy 既不会将它们合并,也不会给出警告,而 haproxy -c 会通过检查,因为它在尝试绑定之前就已退出。您最终会在同一端口上得到两个监听套接字,每条配置行携带着各自不同的 TLS 选项,因此仅存在于其中一条上的设置只会应用于部分流量。请为每个端口只保留一条 bind 行,并将共享的 TLS 设置放在 ssl-default-bind-options 中。

添加 ssl-min-ver TLSv1.2 能起到加固作用吗?

单靠它本身并不能。HAProxy 的文档说明 ssl-min-ver 的默认值本来就已经是 TLSv1.2,因此将其设置为相同的值只是记录了您的意图,并不会改变实际行为。真正的加固来自密码套件设置,请记住 TLS 1.3 需要 ssl-default-bind-ciphersuites,而 TLS 1.2 及以下版本使用的是 ssl-default-bind-ciphers。

我的后端服务器也需要证书吗?

在标准配置中不需要。HAProxy 在边缘终止 TLS 并转发纯 HTTP,这正是为什么只有负载均衡器持有证书。只有当您的策略要求内部环节也需要加密时,才需要在服务器配置行上添加 ssl verify required 及一个 CA 文件。

如何用一个 HAProxy 实例为多个域名提供服务?

将 crt 指向一个目录,而不是单个文件。HAProxy 会加载该目录下的每一个证书,并根据请求使用 SNI 选择合适的证书,因此一条 bind 行就能覆盖所有证书。多域名(SAN)证书是另一种方案,适合一组一起续期的名称。

我应该运行哪个 HAProxy 版本?

3.4 分支于 2026 年 6 月发布,是当前的长期支持版本,维护至 2031 年第二季度。HAProxy 的偶数编号分支是 LTS 分支,维护期约为五年,而像 3.3 这样的奇数编号分支则只有 12 到 18 个月的维护期。本指南中的所有内容都适用于 3.0 及更高版本;只有内置 ACME 客户端需要 3.2 或更新版本。

如果证书已安装,但浏览器仍然报错,我们关于常见 SSL 错误的指南涵盖了通常的原因,最常见的是证书链不完整,或证书未列出相应的名称。

立即订购 SSL 证书, 可节省 10% 的费用!

快速发行, 强大加密, 99.99% 的浏览器信任度, 专业支持和 25 天退款保证. 优惠券代码 SAVE10

龙飞行的详细图像
撰写人

经验丰富的内容撰稿人, 擅长 SSL 证书. 将复杂的网络安全主题转化为清晰, 引人入胜的内容. 通过有影响力的叙述, 为提高数字安全作出贡献.

Avatar of Sergiu Rosca
Technical Review by Sergiu Rosca

Sergiu Rosca is the core web developer behind SSL Dragon. He manages the technical infrastructure, platform performance, and backend integrations that keep the site running smoothly and securely. At SSL Dragon, Sergiu shares practical insights on web development, site optimization, and technical troubleshooting.

All SSL Dragon installation guides are tested on live server environments and undergo a strict peer-review process to ensure your infrastructure remains secure. Read our full Editorial Policy.