HyperText Transfer Protocol (HTTP), the protocol that defines the language browsers use to communicate with web servers, is stateless, meaning that after you make a web request and a server sends back a response, no memory of that interaction remains. To make anything more sophisticated than a static web page work, websites need other ways to remember previous interactions with users.

These days, Javascript frameworks like Vue.js, React.js, and others let developers create single-page applications (SPAs) that provide statefulness to the otherwise stateless web. Because they’re implemented as Javascript applications running in the user’s browser, they can keep track of what the user has done and render the app in a way that accounts for that shared history. However, the user’s browser is not a secure location for storing confidential information or data that the user shouldn’t have control of.

When they need secure storage, developers turn to saving data on the web server. On the server, developers can use session storage, which is an in-memory cache for storing data that can be accessed quickly. It’s the ideal place for saving temporary user information, such as whether the user is currently logged in.

Session storage is typically kept in the runtime memory of the server and, as a consequence of that, requires each user to connect to the same server where their particular session was created. The difficulty arises when you add load balancing into the mix, which, by definition, aims to split up requests across many backend servers, unaware of the fact that a person’s session is stored on only one of them.

How load balancing affects session data

A load balancer allows a website to scale out its capacity to handle more users by dispersing requests across a group of servers that all share the work. However, if your session was created on Server A, but your next request is load balanced to Server B, then Server B will have no idea who you are. It will need to create a new session for you, which will lose your current state, requiring you to log in again, for example.

A load balancer wants to split up a user’s requests across different servers, but that user’s session exists on only one of them. What’s the solution?

In this article, I’ll show you how to enable sticky sessions in your HAProxy load balancer, which helps HAProxy to always send a user back to the same server where their session is stored. However, before I do, I’ll give a disclaimer: while sticky sessions solves the problem, because it forces HAProy to keep sending a user to the same server, it is at odds with the goal of load balancing. Other, more scalable solutions exist.

I recommend storing sessions in a database that’s accessible to all servers. Databases like Redis excel at this, and many server-side web frameworks such as Express.js support it (check out the compatible databases for express-session). That way, no matter which web server the load balancer sends a user to, that server can fetch their session. You can then go back to load balancing across all servers normally.

However, sometimes people require sticky sessions for one reason or another, and it’s good to know that HAProxy supports it.

Do you use HAProxy Enterprise? Click here to learn about sticky sessions in HAProxy Enterprise.

Implement sticky sessions with a cookie

HAProxy can save a cookie in the user’s browser to remember which server to send them back to. The cookie, which contains the server’s unique ID, offers the most accurate way to implement sticky sessions because a cookie is guaranteed to belong to only one user.

Edit your HAProxy configuration file, /etc/haproxy/haproxy.cfg. To create the cookie in the user’s browser, add a cookie directive to the backend section that contains your web servers and add a cookie parameter to each server line to set the cookie’s value:

If the user was first relayed to the web1 server, then their cookie will contain the value web1 and HAProxy will know to keep sending them there. The insert parameter creates the cookie, indirect removes the cookie on each incoming request before forwarding the message to the server, and nocache sets the Cache-Control: private HTTP header so that cache servers between HAProxy and the user won’t cache the response.

If your list of servers is generated dynamically, for example through DNS service discovery, then you can use the dynamic parameter to indicate that the cookie’s value should be a combination of the server’s IP address, port, and a secret key set with dynamic-cookie-key. In the example below, we’re leveraging DNS service discovery and generating the cookie’s value dynamically:

Implement sticky sessions with the client’s IP

While cookies offer the most accurate way to match a user with the server that has their session, HAProxy can also leverage the user’s IP address for this purpose. This method works for non-HTTP applications too.

Beware that IP addresses aren’t always unique to a single user, particularly when network address translation (NAT) is in use, such as for users originating from behind a corporate proxy or when an ISP allocates a pool of public IP addresses for customers. In practice, this means that more users might be routed to the same server, causing load to be distributed more unevenly. Also, a user’s IP address can change, such as when DHCP leases them a new one, in which case their session would be lost. Even so, IP addresses provide a good-enough option for sticking users to a server when a cookie isn’t an option.

Start by creating a peers section containing a stick table that stores user IP addresses.

Then, in your backend section, use the stick match and stick store-request directives to save the user’s source IP address in the table and match it with the server they used.

Add similar to the following to your /etc/haproxy/haproxy.cfg file:

You can use a peers section like this to declare the stick table, like we’ve done here, even when you manage only one HAProxy instance, but it really comes in handy when you manage multiple HAProxy instances in active-active or active-standby mode. You can add more server lines to the peers section to share the stick table storage with the other HAProxy instances, so that the other load balancers will have the server-matching data stored too.

If you’ve explored the HAProxy configuration documentation in depth, you may have come across the directive balance source, which also offers a way to stick a user to the first server they used. However, it determines which server to use by dividing the weight of all servers—weight is a parameter you can assign to servers to send more or less traffic to them—and then choosing a server based on a hash of the user’s IP address. This method, which relies on the total weight of all existing servers, can kick out many users if even one server fails, redistributing users with the recalculated hashes. Stick tables generally present a more stable option, since they will redispatch only the users that were using the server that failed.


In this blog post, you learned how HAProxy supports sticky sessions. You can choose to implement them either with a cookie or with the user’s IP address. While sticky sessions are sometimes necessary, other, more scalable solutions exist, such as storing session data on a separate database.

Interested to know when we publish content like this? Subscribe to our blog! You can also follow us on Twitter and join the conversation on Slack.

To learn more about stick tables, sign up for the on-demand webinar, Introduction to HAProxy Stick Tables.