HAProxy can actually benefit from being a bit "slow" itself to gracefully handle slow HTTP attacks.

Let’s watch HAProxy in action, serving up a simulated slow connection. Imagine a client initiating a request but only sending headers slowly, one byte at a time, or taking minutes between sending subsequent chunks of a large POST body. A naive web server might keep this connection open indefinitely, consuming resources and eventually failing under load.

Here’s a simplified scenario. We’ll use socat to simulate a slow client and haproxy to act as the front-end.

First, set up HAProxy. A basic configuration might look like this:

global
    log /dev/log local0
    maxconn 2000
    daemon

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms  # Generous client timeout
    timeout server 50000ms  # Generous server timeout
    timeout http-request 5000ms
    timeout http-keep-alive 5000ms

listen http_front
    bind *:80
    balance roundrobin
    server http_back 127.0.0.1:8080 check

And a simple backend listening on port 8080:

# backend.py
from http.server import HTTPServer, SimpleHTTPRequestHandler

class Handler(SimpleHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.end_headers()
        self.wfile.write(b"Hello from backend!\n")

if __name__ == "__main__":
    httpd = HTTPServer(('127.0.0.1', 8080), Handler)
    httpd.serve_forever()

Now, let’s simulate a slow client using socat. This command will connect to HAProxy on port 80, send the GET / HTTP/1.1\r\nHost: example.com\r\n\r\n request, but then pause for 30 seconds before sending anything else (which it never will in this demo).

# In one terminal, start the HAProxy and backend
# In another terminal, run the slow client
echo -e "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" | socat - TCP:127.0.0.1:80,sendwait=30

If you watch your HAProxy logs (/var/log/haproxy.log or wherever you configured it), you’ll see connections being established and then eventually timed out. The timeout client 50000ms in the HAProxy config is crucial here. It defines the maximum time HAProxy will wait for a client to send data. When the 50 seconds are up, HAProxy will forcibly close the connection, freeing up resources.

The core problem these attacks exploit is the consumption of connection slots. A naive server might accept a connection and keep it open indefinitely, even if the client is barely sending data. This ties up a worker process or thread, and a large number of such connections can exhaust the server’s capacity.

HAProxy mitigates this by having granular timeouts. The key ones are:

  • timeout client: The maximum inactivity time allowed on the client side of the connection. This is your primary defense. If the client stops sending data for this duration, HAProxy cuts the connection.
  • timeout http-request: The maximum time HAProxy will wait to receive the full HTTP request headers from the client. This is important for attacks that send headers slowly.
  • timeout server: The maximum inactivity time allowed on the server side. This prevents HAProxy from holding onto a connection to a backend that has gone silent.
  • timeout connect: The maximum time HAProxy will wait to establish a connection to a backend server.

To protect against Slowloris, you want to set timeout client and timeout http-request to values that are long enough for legitimate clients but short enough to prevent resource exhaustion. A common starting point is 50 seconds for timeout client and 5 seconds for timeout http-request.

For attacks that send large POST bodies slowly, timeout client is again the main defense. If the client is taking too long to send the request body, timeout client will eventually trigger.

The maxconn global directive limits the total number of concurrent connections HAProxy will accept. While not directly a slow HTTP attack mitigation, it acts as a hard cap to prevent HAProxy itself from being overwhelmed by any type of excessive connection, slow or fast.

A subtle but powerful aspect of HAProxy’s defense is that it doesn’t try to be "smart" about why a client is slow. It simply enforces time limits. This makes it robust against variations of slow HTTP attacks where the attacker might change their timing patterns or the specific part of the HTTP request they are delaying. The timeout http-keep-alive setting also plays a role by limiting how long idle keep-alive connections will be held open, further reducing the attack surface.

If you’re seeing timeout client errors in your HAProxy logs, it means your timeout client value might be too low for some legitimate users, or you are indeed under a slow HTTP attack. Conversely, if you are experiencing resource exhaustion on your backend servers and HAProxy logs show connections being established but not actively used, you might need to adjust your timeout server or timeout client settings.

The next logical step after securing HAProxy against slow HTTP attacks is to consider rate limiting for specific IPs or requests.

Want structured learning?

Take the full Haproxy course →