Load Balancing Exchange with HAProxy

During a recent Exchange migration, I faced a challenge familiar to many IT pros: bandwidth usage surged during mailbox syncs, and I needed a way to load balance Exchange traffic reliably—without the high cost of commercial load balancers.

Why I needed a cheaper load balancer

During a bulk email migration to Exchange Online from Exchange Server 2016/2019, our load balancer couldn’t handle the temporary surge in traffic without a pricey license upgrade. I didn’t want to invest in expensive licenses for what was essentially a one-time event lasting a few weeks, but I still needed:

  • High availability for users and migration tools
  • The ability to distribute connections across several Exchange servers
  • Minimal downtime and smooth cutover

That’s when I thought, that’s another mission for HAProxy.

What is HAProxy?

HAProxy is an open-source, enterprise-grade load balancer and proxy. It’s fast, flexible, and—most importantly for this project—free. You can run it on a Linux VM, even with modest resources.

The Solution: HAProxy in Front of Exchange

Here’s what I did:

  • Deployed HAProxy on a lightweight Linux Debian 12 VM.
  • Configured it to listen on ports 443 (HTTPS) and 80 (HTTP).
  • Redirect any http traffic to https, because none should use http.
  • Terminated SSL at HAProxy (one certificate for the world).
  • Forwarded requests to multiple Exchange 2016/2019 servers behind the scenes.
  • Set up health checks, sticky sessions, and HTTP-to-HTTPS redirects.

This allowed all migration clients, Outlook, and webmail users to keep working—no expensive hardware or licensing required.

Enough talk, show me the configuration

Here’s a simplified snippet showing SSL termination and backend server health checks:

# Global settings
global
    log /dev/log local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
    stats timeout 30s
    user haproxy
    group haproxy
    daemon
    maxconn 8000
    ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
    ssl-default-bind-ciphers 'HIGH:!aNULL:!MD5'

# Default settings
defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    timeout connect 5000
    timeout client  90000
    timeout server  90000

# Statistics backend
listen stats
    bind *:8404
    mode http
    stats enable
    stats uri /stats
    stats refresh 10s

# HTTP to HTTPS redirect
frontend exchange_http
    bind *:80
    mode http
    redirect scheme https code 301 if !{ ssl_fc }

# HTTPS frontend with path-based backend selection & /ecp IP restriction
frontend exchange_https
    bind *:443 ssl crt /etc/ssl/private/haproxy.pem
    mode http
    option forwardfor
    acl xmail hdr(host) -i webmail.mycompany.com autodiscover.mycompany.com

    # /ecp subnet restriction
    acl path_ecp path_reg -i ^/ecp(/|$)
    acl allowed_ecp_src src 10.1.2.0/24 192.168.100.0/24
    http-request deny if path_ecp !allowed_ecp_src

    # Service path acls (case-insensitive)
    acl path_autodiscover      path_reg -i ^/autodiscover(/|$)
    acl path_mapi              path_reg -i ^/mapi(/|$)
    acl path_rpc               path_reg -i ^/rpc(/|$)
    acl path_owa               path_reg -i ^/owa(/|$)
    acl path_activesync        path_reg -i ^/microsoft-server-activesync(/|$)
    acl path_ews               path_reg -i ^/ews(/|$)

    use_backend be_autodiscover if xmail path_autodiscover
    use_backend be_mapi         if xmail path_mapi
    use_backend be_rpc          if xmail path_rpc
    use_backend be_owa          if xmail path_owa
    use_backend be_activesync   if xmail path_activesync
    use_backend be_ews          if xmail path_ews
    use_backend be_ecp          if xmail path_ecp

    default_backend be_default

# Dedicated backend for /autodiscover
backend be_autodiscover
    mode http
    balance source
    stick-table type ip size 200k expire 30m
    stick on src
    option httpchk GET /autodiscover/healthcheck.htm
    http-check expect status 200
    server exchange01 10.11.12.1:443 ssl verify none check
    server exchange02 10.11.12.2:443 ssl verify none check
    server exchange03 10.11.13.1:443 ssl verify none check
    server exchange04 10.11.13.2:443 ssl verify none check

# Dedicated backend for /mapi
backend be_mapi
    mode http
    balance source
    stick-table type ip size 200k expire 30m
    stick on src
    option httpchk GET /mapi/healthcheck.htm
    http-check expect status 200
    server exchange01 10.11.12.1:443 ssl verify none check
    server exchange02 10.11.12.2:443 ssl verify none check
    server exchange03 10.11.13.1:443 ssl verify none check
    server exchange04 10.11.13.2:443 ssl verify none check

# Dedicated backend for /rpc
backend be_rpc
    mode http
    balance source
    stick-table type ip size 200k expire 30m
    stick on src
    option httpchk GET /rpc/healthcheck.htm
    http-check expect status 200
    server exchange01 10.11.12.1:443 ssl verify none check
    server exchange02 10.11.12.2:443 ssl verify none check
    server exchange03 10.11.13.1:443 ssl verify none check
    server exchange04 10.11.13.2:443 ssl verify none check

# Dedicated backend for /owa
backend be_owa
    mode http
    balance source
    stick-table type ip size 200k expire 30m
    stick on src
    option httpchk GET /owa/healthcheck.htm
    http-check expect status 200
    server exchange01 10.11.12.1:443 ssl verify none check
    server exchange02 10.11.12.2:443 ssl verify none check
    server exchange03 10.11.13.1:443 ssl verify none check
    server exchange04 10.11.13.2:443 ssl verify none check

# Dedicated backend for /microsoft-server-activesync
backend be_activesync
    mode http
    balance source
    stick-table type ip size 200k expire 30m
    stick on src
    option httpchk GET /microsoft-server-activesync/healthcheck.htm
    http-check expect status 200
    server exchange01 10.11.12.1:443 ssl verify none check
    server exchange02 10.11.12.2:443 ssl verify none check
    server exchange03 10.11.13.1:443 ssl verify none check
    server exchange04 10.11.13.2:443 ssl verify none check

# Dedicated backend for /ews
backend be_ews
    mode http
    balance source
    stick-table type ip size 200k expire 30m
    stick on src
    option httpchk GET /ews/healthcheck.htm
    http-check expect status 200
    server exchange01 10.11.12.1:443 ssl verify none check
    server exchange02 10.11.12.2:443 ssl verify none check
    server exchange03 10.11.13.1:443 ssl verify none check
    server exchange04 10.11.13.2:443 ssl verify none check

# Backend for /ecp (restricted by IP in frontend)
backend be_ecp
    mode http
    balance source
    stick-table type ip size 200k expire 30m
    stick on src
    option httpchk GET /ecp/healthcheck.htm
    http-check expect status 200
    server exchange01 10.11.12.1:443 ssl verify none check
    server exchange02 10.11.12.2:443 ssl verify none check
    server exchange03 10.11.13.1:443 ssl verify none check
    server exchange04 10.11.13.2:443 ssl verify none check

# Default backend
backend be_default
    mode http
    balance source
    stick-table type ip size 200k expire 30m
    stick on src
    option httpchk GET /owa/healthcheck.htm
    http-check expect status 200
    server exchange01 10.11.12.1:443 ssl verify none check
    server exchange02 10.11.12.2:443 ssl verify none check
    server exchange03 10.11.13.1:443 ssl verify none check
    server exchange04 10.11.13.2:443 ssl verify none check
  • SSL certificate is only needed on the HAProxy VM (I use a SAN for webmail and autodiscover).
  • Health checks ensure only live Exchange servers get traffic.
  • Sticky sessions (by client IP) make sure connections don’t bounce between servers (required in Exchange world).

Lessons learned?

  • HAProxy handled bulk migration and user traffic like a champ. No expensive upgrades required.
  • For ongoing production use, I’d recommend tuning, monitoring, and using at least two HAProxy nodes for true redundancy.
  • Remember to secure your Exchange and HAProxy servers, keep certificates up to date, and regularly test your failover.
  • Soon, the email will be Microsoft responsibility, and I will move away from supporting it.

If you need a fast, free, and reliable way to load balance Exchange during migrations (or even permanently for small/medium orgs), HAProxy is a fantastic option. It saved us significant cost and delivered rock-solid performance for our busy migration window.

Gregory

Gregory

I'm Gregory from Switzerland, and this is a running log of thoughts, findings, and lessons learned over more than 20 years in IT. With a deep passion for networks and security, I focus on architecture, governance, and emerging technologies. My journey has taken me through complex challenges and continuous learning across various sectors. While this space mainly serves as my personal knowledge base, I hope that sharing these notes might also offer insights or inspiration to others navigating the ever-evolving digital landscape.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *

3 + 3 =