> For the complete documentation index, see [llms.txt](https://docs.helmut.de/helmut4-releases/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.helmut.de/helmut4-releases/v4.13.0/getting-started/additional-configurations/communication-ssl-proxy-and-ports/load-balancers/high-availability-proxy-haproxy.md).

# High Availability Proxy (HAProxy)

### Introduction

HAProxy is used as a load balancer and reverse proxy in front of the Helmut4 servers. In this setup, HAProxy provides a single entry point for users and services, handles HTTPS traffic, forwards requests to the available Helmut4 backend servers, and performs health checks to make sure traffic is only routed to reachable systems.

This guide describes how HAProxy can be added to an existing Docker Swarm environment and used together with Helmut4. The setup also uses Keepalived to provide a virtual IP address, which allows clients to connect to a stable address even if the active Docker manager node changes.

***

### Portainer Stack Example

<details>

<summary>Keep alive &#x26; HAProxy (Compose for Stack)</summary>

<pre class="language-yaml"><code class="lang-yaml">networks:
  host:
    external: true
    name: host

configs:
  haproxy.conf:
    external: true
  helmut4_server.pem:
    external: true

services:
  keepalived:
    restart: always
    image: ghcr.io/lhns/keepalived-swarm:0.2.7
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /usr/bin/docker:/usr/bin/docker:ro
    networks:
      - host
    deploy:
      mode: global
      placement:
        constraints:
          - node.role == manager
    environment:
      KEEPALIVED_VIRTUAL_IPS: <a data-footnote-ref href="#user-content-fn-1">10.123.117.89</a>

  haproxy:
    image: haproxytech/haproxy-alpine:3.3
    restart: always
    configs:
      - source: haproxy.conf
        target: /usr/local/etc/haproxy/haproxy.cfg
      - source: helmut4_server.pem
        target: /etc/ssl/certs/server.pem
    networks:
      - host
    depends_on:
      - keepalived
    deploy:
      mode: global
      placement:
        constraints:
          - node.role == manager
</code></pre>

The latest versions of the dedicated images can be found here:

* Keep Alive: <https://github.com/lhns/docker-swarm-keepalived/releases>
* HAProxy: <https://hub.docker.com/r/haproxytech/haproxy-alpine>

</details>

***

### HAProxy Configuration Example

<details>

<summary>haproxy.conf</summary>

<pre class="language-haproxy"><code class="lang-haproxy">global
    log stdout format raw local0 info
    ca-base /etc/ssl/certs
    crt-base /etc/ssl/private
    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:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
    ssl-server-verify none
    h1-case-adjust content-length Content-Length
    h1-case-adjust version Version
    h1-case-adjust content-type Content-Type

defaults
    mode http
    log global
    timeout client 1000s
    timeout connect 5000s
    timeout server 5000s
    timeout http-request 1000s

frontend stats
    bind *:8083
    mode http
    option http-keep-alive
    stats enable
    stats uri /
    stats refresh 5s
    stats auth admin:AdMiN123
    stats admin if TRUE
    stats show-legends
    stats show-modules
    stats show-node
    stats realm HAProxy\ Statistics

frontend helmut_frontend_ssl
    mode http
    bind *:80
    bind *:443 ssl crt /etc/ssl/certs/server.pem
    option http-keep-alive

    http-request redirect scheme https unless { ssl_fc }

    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    http-request set-header X-Forwarded-Proto http unless { ssl_fc }
    http-request set-header X-Forwarded-For %[src]
    http-request set-header X-Real-IP %[src]

    default_backend helmutservers_ssl

backend helmutservers_ssl
    mode http
    balance roundrobin
    default-server inter 5s fall 3 rise 2

    option httpchk GET /v1/fx/version
    http-check expect rstring ^.*version.*:.*4.*$

    timeout tunnel 60s

    stick-table type ip size 1m expire 30m
    stick on src

    server helmut1 <a data-footnote-ref href="#user-content-fn-2">helmut-server-01.example.com:8443</a> ssl check
    server helmut2 <a data-footnote-ref href="#user-content-fn-2">helmut-server-02.example.com:8443</a> ssl check
    server helmut3 <a data-footnote-ref href="#user-content-fn-2">helmut-server-03.example.com:8443</a> ssl check
</code></pre>

{% hint style="info" %}
Please replace the placeholder DNS name `helmut-server-01.example.com:8443` with the actual DNS name and external port used in your environment.
{% endhint %}

</details>

***

### Certificate File: PEM

For HTTPS, HAProxy requires a certificate file that contains both the public certificate and the matching private key.

In this setup, only the following certificate file is needed:

```
helmut4_server.pem
```

The `.pem` file is used directly by HAProxy for SSL/TLS termination. It must contain both the certificate and the private key in the same file.

HAProxy references this file in the `bind` statement:

```haproxy
bind *:443 ssl crt /etc/ssl/certs/server.pem
```

In the Docker Compose file, the PEM file is mounted into the HAProxy container as a Docker config:

```yaml
configs:
  - source: helmut4_server.pem
    target: /etc/ssl/certs/server.pem
```

The file mounted at `/etc/ssl/certs/server.pem` is the certificate file HAProxy uses for HTTPS connections.

A valid PEM file should contain the certificate and private key blocks, for example:

```
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----

-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
```

or:

```
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----

-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
```

***

### How the Setup Works

Keepalived provides the virtual IP address:

```yaml
KEEPALIVED_VIRTUAL_IPS: 10.123.117.89
```

Clients connect to this virtual IP address instead of connecting directly to a specific Helmut4 server.

HAProxy listens on ports `80` and `443`:

```haproxy
bind *:80
bind *:443 ssl crt /etc/ssl/certs/server.pem
```

HTTP traffic on port `80` is automatically redirected to HTTPS:

```haproxy
http-request redirect scheme https unless { ssl_fc }
```

HAProxy then forwards the request to one of the configured Helmut4 backend servers:

```haproxy
server helmut1 helmut-server-01.example.com:8443 ssl check
server helmut2 helmut-server-02.example.com:8443 ssl check
server helmut3 helmut-server-03.example.com:8443 ssl check
```

{% hint style="info" %}

## Helmut4 - Exposed Taefik Ports

It is necessary to change the exposed ports of the Helmut4 Traefik container to different values, because HAProxy itself uses ports `80` and `443`.

This change is only required if HAProxy is running on the same host as Helmut4.

A step-by-step guide for changing the ports can be found here: [Change HTTP/HTTPS Ports](/helmut4-releases/v4.13.0/getting-started/additional-configurations/communication-ssl-proxy-and-ports/change-http-https-ports.md)
{% endhint %}

The backend servers are checked using the Helmut4 version endpoint:

```haproxy
option httpchk GET /v1/fx/version
http-check expect rstring ^.*version.*:.*4.*$
```

Only servers that return the expected response are considered healthy and receive traffic.

***

### Session Stickiness

The configuration uses source-IP-based stickiness:

```haproxy
stick-table type ip size 1m expire 30m
stick on src
```

This means that requests from the same client IP are routed to the same backend server for 30 minutes. This can be useful when backend-side session behavior or temporary state should stay on the same Helmut4 server during a user session.

***

### HAProxy Statistics Page

The configuration also enables the HAProxy statistics interface on port `8083`:

```haproxy
frontend stats
    bind *:8083
    stats enable
    stats uri /
    stats refresh 5s
    stats auth admin:AdMiN123
```

This page can be used to monitor backend server status, active connections, health checks, and traffic distribution.

For production environments, the username and password should be changed and access to the statistics page should be restricted.

[^1]: Replace with actual IP address

[^2]: Replace with actual DNS name and port
