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.