SSL/TLS

Enable TLS by using the ACME protocol

This page applies to:

  • HAProxy 3.2 and newer
  • HAProxy Enterprise 3.2r1 and newer

Experimental feature

Enabling the ACME protocol to manage TLS certificates is currently experimental.

With the ACME (Automatic Certificate Management Environment) protocol, you can integrate with certificate issuers to automate the process of obtaining and renewing TLS certificates. The ACME protocol is an open standard that’s widely supported by both free and paid certificate issuers. By enabling ACME at the proxy layer, you can:

  • Offload the integration details and avoid a custom implementation in your applications.
  • Ensure a single tier for managing the certificate issuer’s validation of your domain and the process of certificate renewals.
  • Maintain a consistent approach for certificate management across heterogeneous applications.

This feature works with a single load balancer, but not an active-active pair, due to the need to communicate with the certificate issuer’s servers.

Let’s Encrypt Jump to heading

In this section, learn how to configure the load balancer to use the ACME provider named Let’s Encrypt to get a TLS certificate.

HTTP-01 challenge Jump to heading

This section applies to:

  • HAProxy 3.2 and newer
  • HAProxy Enterprise 3.2r1 and newer

We’ll use an HTTP-01 challenge type. The challenge allows the Let’s Encrypt servers to verify that you control the domain for which you’re requesting a certificate. In this case, HAProxy hosts a file at a well-known URL path, which Let’s Encrypt verifies.

To get a certificate from Let’s Encrypt:

  1. HAProxy 3.2 / HAProxy Enterprise 3.2: In version 3.2, you must generate a dummy TLS certificate. This certificate serves as a placeholder, allowing HAProxy to start and preventing an error that would happen if it tried to read a certificate that doesn’t yet exist on the filesystem. You don’t need to do this in later versions.

    • Generate a dummy TLS certificate file, which we’ll later overwrite with the Let’s Encrypt certificate. The load balancer needs a file on the filesystem at startup.

      nix
      cd ~
      openssl req -x509 \
      -newkey rsa:2048 \
      -keyout example.key \
      -out example.crt \
      -days 365 \
      -nodes \
      -subj "/C=US/ST=Ohio/L=Columbus/O=MyCompany/CN=www.example.com"
      cat example.key example.crt > example.pem
      nix
      cd ~
      openssl req -x509 \
      -newkey rsa:2048 \
      -keyout example.key \
      -out example.crt \
      -days 365 \
      -nodes \
      -subj "/C=US/ST=Ohio/L=Columbus/O=MyCompany/CN=www.example.com"
      cat example.key example.crt > example.pem
    • Copy the PEM file to the directory. For example:

      nix
      sudo mkdir /etc/haproxy/ssl
      sudo cp example.pem /etc/haproxy/ssl
      nix
      sudo mkdir /etc/haproxy/ssl
      sudo cp example.pem /etc/haproxy/ssl
  2. HAProxy 3.3 / HAProxy Enterprise 3.3 and newer: Starting in version 3.3, HAProxy generates event notifications when it creates or updates a certificate from an ACME provider, and the HAProxy Data Plane API watches for these events. Together, they automate saving the certificates to the filesystem. Previously, you had to call the Runtime API function dump ssl cert to save the certificate from HAProxy’s runtime memory to a file.

    • Install the HAProxy Data Plane API.

    • Find the path to the HAProxy master socket by calling systemctl status haproxy to get the value of the -S parameter.

      nix
      systemctl status haproxy
      nix
      systemctl status haproxy
      output
      text
      ● haproxy.service - HAProxy Load Balancer
      Loaded: loaded (/usr/lib/systemd/system/haproxy.service; enabled; preset: enabled)
      Active: active (running) since Mon 2026-04-06 14:39:32 UTC; 33min ago
      Docs: man:haproxy(1)
      file:/usr/share/doc/haproxy/configuration.txt.gz
      Main PID: 2366 (haproxy)
      Status: "Ready."
      Tasks: 3 (limit: 9433)
      Memory: 69.2M (peak: 70.8M)
      CPU: 63ms
      CGroup: /system.slice/haproxy.service
      ├─2366 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock
      └─2368 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock
      output
      text
      ● haproxy.service - HAProxy Load Balancer
      Loaded: loaded (/usr/lib/systemd/system/haproxy.service; enabled; preset: enabled)
      Active: active (running) since Mon 2026-04-06 14:39:32 UTC; 33min ago
      Docs: man:haproxy(1)
      file:/usr/share/doc/haproxy/configuration.txt.gz
      Main PID: 2366 (haproxy)
      Status: "Ready."
      Tasks: 3 (limit: 9433)
      Memory: 69.2M (peak: 70.8M)
      CPU: 63ms
      CGroup: /system.slice/haproxy.service
      ├─2366 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock
      └─2368 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock
    • Edit the HAProxy Data Plane API configuration file. On HAProxy, it’s located at /etc/dataplaneapi/dataplaneapi.yml. On HAProxy Enterprise, it’s located at /etc/hapee-extras/dataplaneapi.yml. Under the haproxy section, set the master_runtime field to the HAProxy master socket path. For example:

      dataplaneapi.yml
      yaml
      haproxy:
      master_runtime: /run/haproxy-master.sock
      dataplaneapi.yml
      yaml
      haproxy:
      master_runtime: /run/haproxy-master.sock
  3. Edit your load balancer configuration accordingly:

    • In the global section, add expose-experimental-directives and httpclient.resolvers.prefer ipv4:

      haproxy
      global
      ...
      expose-experimental-directives
      httpclient.resolvers.prefer ipv4
      haproxy
      global
      ...
      expose-experimental-directives
      httpclient.resolvers.prefer ipv4
    • After the global section, add an acme section to register with Let’s Encrypt. In this example, we’re using the Let’s Encrypt staging server, which you can use for testing. Later, change it to the Let’s Encrypt production server.

      haproxy
      acme letsencrypt-staging
      directory https://acme-staging-v02.api.letsencrypt.org/directory
      contact admin@example.com
      challenge HTTP-01
      keytype RSA
      bits 2048
      map virt@acme
      haproxy
      acme letsencrypt-staging
      directory https://acme-staging-v02.api.letsencrypt.org/directory
      contact admin@example.com
      challenge HTTP-01
      keytype RSA
      bits 2048
      map virt@acme
    • After the acme section, add a crt-store section to define the location of your Let’s Encrypt issued certificate. You don’t have to use a crt-store section. For small configurations, the arguments can all go onto the ssl-f-use line. The load directive will save the .pem certificate, after it’s generated, to the directory /etc/haproxy/ssl. Change this line to use your domain.

      haproxy
      crt-store my_files
      crt-base /etc/haproxy/ssl
      key-base /etc/haproxy/ssl
      load crt "example.pem" acme letsencrypt-staging domains "www.example.com" alias "example"
      haproxy
      crt-store my_files
      crt-base /etc/haproxy/ssl
      key-base /etc/haproxy/ssl
      load crt "example.pem" acme letsencrypt-staging domains "www.example.com" alias "example"
    • In your frontend section, respond to the Let’s Encrypt challenge via the http-request return directive and use the ssl-f-use directive to serve the TLS certificate bundle.

      haproxy
      frontend mysite
      bind :80
      bind :443 ssl
      http-request return status 200 content-type text/plain lf-string "%[path,field(-1,/)].%[path,field(-1,/),map(virt@acme)]\n" if { path_beg '/.well-known/acme-challenge/' }
      http-request redirect scheme https unless { ssl_fc }
      ssl-f-use crt "@my_files/example"
      use_backend webservers
      haproxy
      frontend mysite
      bind :80
      bind :443 ssl
      http-request return status 200 content-type text/plain lf-string "%[path,field(-1,/)].%[path,field(-1,/),map(virt@acme)]\n" if { path_beg '/.well-known/acme-challenge/' }
      http-request redirect scheme https unless { ssl_fc }
      ssl-f-use crt "@my_files/example"
      use_backend webservers
  4. Restart the load balancer.

    nix
    sudo systemctl restart haproxy
    nix
    sudo systemctl restart haproxy
    nix
    sudo systemctl restart hapee-3.3-lb
    nix
    sudo systemctl restart hapee-3.3-lb
  5. HAProxy 3.2 / HAProxy Enterprise 3.2: Use the HAProxy Runtime API to initiate getting a certificate. In newer versions, this happens automatically when you restart the load balancer.

    • Call the Runtime API command acme renew to create a Let’s Encrypt certificate that replaces, in memory, the dummy TLS certificate file you created earlier.

      nix
      echo "acme renew @my_files/example" | sudo socat stdio tcp4-connect:127.0.0.1:9999
      nix
      echo "acme renew @my_files/example" | sudo socat stdio tcp4-connect:127.0.0.1:9999
    • The new certificate exists only in the load balancer’s running memory. To save it to a file, call the Runtime API command dump ssl cert.

      nix
      echo "dump ssl cert @my_files/example" | sudo socat stdio tcp4-connect:127.0.0.1:9999 | sudo tee /etc/haproxy/example.pem
      nix
      echo "dump ssl cert @my_files/example" | sudo socat stdio tcp4-connect:127.0.0.1:9999 | sudo tee /etc/haproxy/example.pem
    • Use the Runtime API command acme status to see a list of running tasks.

      nix
      echo "acme status" | sudo socat stdio tcp4-connect:127.0.0.1:9999
      nix
      echo "acme status" | sudo socat stdio tcp4-connect:127.0.0.1:9999
      output
      text
      # certificate section state expiration date (UTC) expires in scheduled date (UTC) scheduled in
      @my_files/example letsencrypt-staging Running 2026-09-30T20:37:44Z 364d 23h50m02s - -
      output
      text
      # certificate section state expiration date (UTC) expires in scheduled date (UTC) scheduled in
      @my_files/example letsencrypt-staging Running 2026-09-30T20:37:44Z 364d 23h50m02s - -

The TLS certificate should now be saved to the /etc/haproxy/ssl directory. It will be used when clients make requests to your domain.

Do you have any suggestions on how we can improve the content of this page?