如何在 Ubuntu VPS 上使用 Docker Compose 安装 HAProxy
在 VPS 上直接暴露一个网络服务很容易——直到你想要一个干净的公共入口、稍后交换后端的自由,或者一个更安全的方式来停止向故障的东西发送流量。这就是代理从”大型基础设施团队的东西”变成实用工具的时刻。

HAProxy 非常适合这个角色。可以把它看作坐在你的应用程序前面的流量管理器:请求首先到达 HAProxy,然后 HAProxy 决定它们应该去哪里。你不需要一个大型集群来从中受益。即使在一个 Ubuntu 24.04 VPS 上,它也能给你一个更干净的互联网和你实际运行的服务之间的边界。
本指南有意保持第一次部署的结构化:一个 Ubuntu 24.04 VPS、Docker Compose、一个 HAProxy 容器、一个演示后端,以及路由确实有效的证明。
为什么在需要 HAProxy 之前就应该了解它
想象一个小型 VPS 运行一个应用,现在工作得很好。它在一个端口上响应,网站加载,一切看起来都不错。当你想要一个稳定的公共入口点、稍后替换后端而不改变公共地址的选项,或者一个可以停止向故障服务发送流量的前层时,摩擦就开始了。直接暴露应用很快就会开始感觉脆弱。

这些需求都指向同一个缺失的层:互联网和你的应用之间的受控入口点。HAProxy 提供了这一层。客户端首先连接到 HAProxy,然后 HAProxy 决定每个请求接下来去哪里。
即使在你有多个服务器之前,这种分离也很有用。它现在为你提供了一个更清晰的公共边界,以及一条更安全的路径来进行后续更改,例如后端替换、健康感知路由和 HTTPS。本指南的其余部分以其最简单的工作形式展示了这种模式,并通过真实的请求路径验证了它。
快速 HAProxy 术语,让本指南的其余部分更容易理解

您只需要一个小的词汇集就能自信地遵循第一次 HAProxy 部署。下表涵盖了本指南中重要的术语。
| 术语 | 简明英文含义 |
|---|---|
| 🌐 反向代理 | 一个面向前端的服务,首先接收请求并将其传递给另一个内部服务。 |
| ⚖️ 负载均衡器 | 一个前层,可以将请求分配到多个后端目标。 |
| 🚪 前端 | 客户端连接到 HAProxy 的地方。 |
| 🧩 后端 | HAProxy 接下来将请求发送到的服务或服务器。 |
| ❤️ 健康检查 | HAProxy 用来检测后端是否应该继续接收流量的一种方式。 |
| 🐳 镜像 | 用于创建容器的打包应用程序模板。 |
| 📦 容器 | 镜像的运行实例。 |
对于本指南,反向代理是首先要记住的心智模型。HAProxy 位于其他东西的前面并控制交接。负载均衡是当您稍后添加多个后端服务器时变得有用的扩展功能。
打开配置后最重要的两个术语是 前端 和 后端。前端 是客户端到达的地方。后端 是 HAProxy 接下来发送请求的地方。健康检查 很重要,因为它让 HAProxy 能够注意到何时目标应该停止接收流量。
HAProxy 的优势——以及本指南有意跳过的内容

如果你把你的堆栈想象成一座办公楼,HAProxy 就是前台:流量首先到达那里,被引导到正确的房间,并停止被发送到明显不可用的房间。
在本指南中,这转化为三个初学者相关的工作:
- 接受传入的 HTTP 请求
- 将它们转发到演示后端
- 监控该后端是否足够健康以继续接收流量
即使只有一个后端,这也很有用,因为它在应用程序前面为你提供了一个受控的公共边界。
稍后,相同的模式可以干净地扩展。你可以替换后端、添加更多后端、引入 HTTPS,或让 HAProxy 将流量分散到多个目标而不仅仅是一个。为了保持第一遍的可教性,本指南停留在 HTTP 模式,有意跳过 TLS 终止、ACL、速率限制、粘性表和 HA 对。这些都是真实的 HAProxy 主题。它们只是不是第一次工作部署的正确起点。
你正在构建什么以及首先需要什么

在创建文件之前,查看堆栈的最终形状会很有帮助。本指南中的部署如下所示:
Client browser or curl
|
v
HAProxy frontend (:80)
|
v
demo backend service (demo:5678)
Optional local-only validation:
HAProxy stats frontend (127.0.0.1:8404/stats)Docker Compose 是这里的主要路径,因为它保持首次安装的可重复性、可见性和易于编辑。与其在第一天构建自定义镜像,不如将 HAProxy 配置保留在主机上,将其挂载到容器中,并从一个文件启动整个堆栈。在自管理的 Ubuntu VPS 上(例如 AlexHost VPS),这是一个完美的契合,因为布局保持易于检查。
💡 提示:本指南故意使用 Docker Compose 加上绑定挂载的 haproxy.cfg。这是最透明的首次安装路径,因为你可以直接编辑代理配置,而无需添加镜像构建步骤。
在开始之前,请确保你已准备好以下基础:
- Ubuntu 24.04 VPS
- 已安装 Docker Engine
- Docker Compose v2 可通过 docker compose 获得
- 终端访问权限和运行 Docker 的权限
- 主机上的端口 80 可用
- 如果使用 UFW 或提供商端防火墙规则,允许入站 HTTP
首先,检查你的 Ubuntu 版本
lsb_release -a
接下来,确认 Docker 和现代 Compose 可用:
docker --version
docker compose version
如果两个命令都返回版本信息,容器运行时端已准备好,你可以专注于 HAProxy,而不是绕道进行 Docker 安装。
接下来,确保端口 80 未被使用,然后检查 UFW 是否处于活动状态以及 HTTP 是否已被允许:
sudo ss -tlnp | grep -E ':(80)s' || true
sudo ufw status
sudo ufw allow 80/tcp
✏️ 注意:ss 检查没有输出通常意味着端口 80 是空闲的。如果你看到 nginx、apache2、caddy 或其他服务已在监听该端口,请先修复。这是一个十秒钟的预检步骤,可以节省大量后续的困惑。
在上面的示例中,sudo ufw status 显示 Status: active,80/tcp 已在允许列表中。这就是为什么 sudo ufw allow 80/tcp 返回 Skipping adding existing rule 而不是添加新规则。该输出是正常的,只是意味着防火墙规则已经就位。
创建项目文件夹和 Compose 文件
首先为这个首次部署所需的两个文件创建一个小项目文件夹:
mkdir -p ~/haproxy-docker
cd ~/haproxy-docker
之后,布局应该尽可能简洁:
~/haproxy-docker/
├── compose.yaml
└── haproxy.cfg现在创建 compose.yaml 并使用以下确切内容:
services:
demo:
image: hashicorp/http-echo:1.0
command: ["-listen=:5678", "-text=Hello from the HAProxy demo backend"]
restart: unless-stopped
haproxy:
image: haproxy:3.4.1
depends_on:
- demo
ports:
- "80:80"
- "127.0.0.1:8404:8404"
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
sysctls:
net.ipv4.ip_unprivileged_port_start: "0"
restart: unless-stopped此文件将容器连接在一起,但尚未定义 HAProxy 请求逻辑。它告诉 Docker 要运行哪些镜像、要发布哪些端口,以及 HAProxy 配置将从主机的何处挂载。
以下设置是对干净首次部署最重要的:
| Compose 设置 | 为什么在这里 |
|---|---|
| hashicorp/http-echo:1.0 | 提供一个微小、可预测的演示后端,同时不需要同时教授第二个 Web 服务器。 |
| haproxy:3.4.1 | 使用固定的稳定标签而不是 latest,这使指南随时间推移更加稳定。 |
| depends_on | 在 HAProxy 之前启动 demo 服务,这对首次运行顺序很有帮助。 |
| 80:80 | 在读者期望的标准 Web 端口上发布主 HTTP 监听器。 |
| 127.0.0.1:8404:8404 | 保持统计页面可用于本地验证,默认情况下不公开暴露。 |
| ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro | 将可见的主机端配置文件以只读方式挂载到官方 HAProxy 镜像中。 |
| sysctls 与 net.ipv4.ip_unprivileged_port_start: “0” | 让非 root HAProxy 容器能够绑定到 80 等低端口。 |
| restart: unless-stopped | 提供实用的 VPS 默认值:在故障或重启后重启,但尊重有意的手动停止。 |
这里还有一个细节很重要:此文件中没有自定义 Docker 网络,因为 Docker Compose 会自动创建默认网络。这为您提供了项目内的服务名称 DNS,这就是为什么 HAProxy 将能够通过 demo:5678 到达后端,而无需额外的配置。
⚠️ 警告:端口 80 是特权端口,所以 sysctls 行不是装饰性的。将主机映射更改为 8080:80 不会移除特权端口要求,如果 HAProxy 仍在容器内绑定到 :80。
编写并验证最小化的 haproxy.cfg
容器连接就位后,HAProxy 仍需要指令来说明流量在哪里到达、应该去哪里,以及如何检查后端健康状况。接下来创建 haproxy.cfg:
global
log stdout format raw local0
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
frontend http
bind :80
default_backend demo_backend
backend demo_backend
balance roundrobin
server demo1 demo:5678 check
frontend stats
bind :8404
stats enable
stats refresh 10s
stats uri /stats这是一个最小化配置,但不是一次性的。log stdout format raw local0 是容器友好的日志记录选择,因为 Docker 可以轻松显示 stdout,而 defaults 中的 mode http 使整个示例保持在 HTTP 模式,以便监听器和后端行为保持一致且易于阅读。
✏️ 注意: 在部分分解之前值得指出一个细节:balance roundrobin 被明确设置,因为较新的 HAProxy 版本将默认后端算法更改为 random,而 roundrobin 在第一次通过时更容易以可预测的方式教授。
以下是每个部分的纯英文分解:
| 部分 | 关键行 | 它的作用 |
|---|---|---|
| global | log stdout format raw local0 | 将日志发送到 stdout,以便 Docker 日志记录保持直接。 |
| defaults | mode http、超时 | 建立基线 HTTP 行为和合理的超时值。 |
| frontend http | bind :80、default_backend demo_backend | 创建公共监听器并将其连接到后端定义。 |
| backend demo_backend | balance roundrobin、server demo1 demo:5678 check | 告诉 HAProxy 使用哪个服务并监控其健康状况。 |
| frontend stats | bind :8404、stats enable、stats uri /stats | 添加可选的本地验证页面,以便稍后查看运行时状态。 |
您可能会注意到一件事缺失:option forwardfor。这个遗漏在基本路径中是有意的。保留原始客户端 IP 稍后很有用,但这个第一次部署是关于证明路由和后端健康,而不是教授头部行为,使用不会使该信号特别有价值的演示容器。
💡 提示: 始终在启动完整堆栈之前验证 HAProxy 配置。因为此配置通过其 Compose 服务名称(demo)引用后端,请先启动该后端,以便 HAProxy 在验证期间可以解析它。
从同一项目目录运行验证:
docker compose up -d demo
docker compose run --rm --no-deps haproxy haproxy -V -c -f /usr/local/etc/haproxy/haproxy.cfg
如果第二个命令以 Configuration file is valid 结尾,您已经证明了 HAProxy 可以正确解析文件并在任何实时监听器启动之前解析后端目标。
启动堆栈并证明代理工作
配置验证后,以分离模式启动堆栈:
由于验证步骤已经启动了 demo,此命令主要启动 HAProxy 并协调完整的双服务堆栈:
docker compose up -d
然后检查两个容器是否都在运行:
docker compose ps
该进程视图只是第一个检查点。它确认 Docker 启动了容器,但还不能确认 HAProxy 是否成功将流量路由到后端。下一个请求验证实际的数据路径。
现在从 VPS 本身运行实际的路由测试:
curl -i http://127.0.0.1
成功信号是 HTTP/1.1 200 OK 加上响应体包含 Hello from the HAProxy demo backend。某些 http-echo 构建会将该文本包装在一个小的 HTML 响应中,因此请关注响应体短语而不是确切的格式。
如果您想要浏览器级别的证明,请从另一台机器打开 http://YOUR_SERVER_IP。

为了进行第二个验证,请从 VPS 检查仅本地的统计页面:
curl http://127.0.0.1:8404/stats在统计页面上,最有用的信号是名为 http 的前端、名为 demo_backend 的后端、名为 demo1 的服务器行、显示为 UP 的状态,以及通常像 L4OK in 0ms 这样的最后检查值。还要记住一个小的 Docker 细节:简短语法 depends_on 控制启动顺序,但不会等待服务变为健康状态。如果启动后的第一个 curl 失败一次,请等待几秒钟后重试,然后再假设配置有误。
进程状态和实际成功之间的区别更容易以表格形式理解:
| 状态 | 它告诉您什么 |
|---|---|
| 容器 正在运行 | Docker 启动了进程。 |
| curl -i http://127.0.0.1 返回 200 OK 和演示短语 | HAProxy 实际上正在将流量路由到后端。 |
| 统计页面显示 demo1 为 UP | HAProxy 将后端视为健康。 |
常见首次运行错误和快速修复

如果设置不能立即工作,请抵制住一次重写两个文件的冲动。此堆栈上的大多数首次运行失败都是可预测的,当您一次更改一个变量时,它们会变得更容易修复。
使用此矩阵作为快速诊断层:
HAProxy 立即退出
可能原因: haproxy.cfg 缺失。
快速修复: 确保 haproxy.cfg 存在于 compose.yaml 旁边。
发生原因: 官方镜像不附带现成可用的配置。
错误提示无法打开 /usr/local/etc/haproxy/haproxy.cfg
可能原因: 绑定挂载路径错误。
快速修复: 验证 ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro 完全匹配。
发生原因: HAProxy 没有有效的配置文件无法启动。
错误提示端口 80 上的 Permission denied
可能原因: 特权端口绑定问题。
快速修复: 在 Compose 中保留 net.ipv4.ip_unprivileged_port_start: “0”,或将 HAProxy 和发布端口都移至 8080。
发生原因: 容器以非 root haproxy 用户身份运行。
您将映射更改为 8080:80 但仍然收到绑定错误
可能原因: HAProxy 仍在容器内绑定到 :80。
快速修复: 如果您离开端口 80,请同时更改主机映射和内部 bind 行。
发生原因: 特权端口规则也适用于容器内部。
端口 80 已被占用
可能原因: 另一个服务占用了主机端口。
快速修复: 重新运行 ss 检查并停止或移动冲突的服务。
发生原因: 只有一个进程可以监听同一主机端口。
语法检查报告 unknown keyword 或特定行错误
可能原因: HAProxy 配置拼写错误。
快速修复: 重新运行语法检查并修复它报告的确切行。
发生原因: HAProxy 的解析器很严格,一旦您有意使用它就会很有帮助。
容器已启动,但 curl 不返回演示响应
可能原因: 路由路径错误。
快速修复: 重新检查 default_backend demo_backend、server demo1 demo:5678 check 和 demo 服务名称。
发生原因: 运行中的容器不能证明前端到后端路径正确。
本地 curl 有效,但从外部无法访问该站点
可能原因: 防火墙或提供商安全规则。
快速修复: 在 UFW 和任何提供商端防火墙中打开端口 80。
发生原因: 本地发布可以工作,即使公共访问仍被阻止。
⚠️ 警告: 一次更改一个内容。如果您盲目编辑 compose.yaml 和 haproxy.cfg,您会更难判断失败是文件路径问题、端口问题还是路由问题。
当您需要快速证据时,请将这些命令放在附近:
docker compose logs haproxy
docker compose ps
docker compose up -d demo
docker compose run --rm --no-deps haproxy haproxy -V -c -f /usr/local/etc/haproxy/haproxy.cfg
sudo ss -tlnp | grep -E ':(80|8404)s' || true这是最值得一眼识别的三种警报模式:
[ALERT] ... Cannot open configuration file /usr/local/etc/haproxy/haproxy.cfg : No such file or directory
[ALERT] ... Starting frontend http: cannot bind socket (Permission denied) [0.0.0.0:80]
[ALERT] ... parsing [/usr/local/etc/haproxy/haproxy.cfg:12] : unknown keyword 'chekc'; did you mean 'check' maybe?这是小型首次部署令人放心的部分:失败形状通常也很小。您不需要重新开始。您需要识别哪一层在抱怨,然后首先更正那一个内容。
安装后的后续步骤
一旦单后端演示工作正常,该架构已经很有用了。下一个真正的步骤是用你的实际应用程序替换演示容器,同时保持相同的HAProxy结构。之后,将HTTPS/TLS作为专门的后续步骤添加,并将基于域的路由和ACL视为单独的主题,而不是在第一次安装中仓促处理它们。

当你准备好使用多个后端时,相同的模式变得明显更有意义:
backend app_backend
balance roundrobin
server app1 app1:8080 check
server app2 app2:8080 check对于对绑定挂载配置的安全编辑,请先验证,然后优雅地重新加载HAProxy:
docker compose up -d demo
docker compose run --rm --no-deps haproxy haproxy -V -c -f /usr/local/etc/haproxy/haproxy.cfg
docker compose kill -s HUP haproxy📝 注意:在本指南中,统计页面故意仅限本地访问。如果你曾经公开暴露它,请先添加身份验证和访问控制。
这将你带回最初的问题:你想要一个干净的前门来处理服务,而不是将第一次设置变成一个完整的运维项目。你现在有了一个可行的路径。更重要的是,你还拥有正确的心智模型:HAProxy首先接收流量,将其转发到应该去的地方,并为你提供了一种更干净的方式来在自管理VPS上扩展堆栈,而不会失去对配置的控制。
