A10 Thunder is a capable ADC, but the per-instance licensing model gets expensive fast when you scale horizontally or spin up dev and staging copies of production. HAProxy gives you the L4/L7 load balancing, TLS termination, and persistence you actually use day to day, with no license to count. The catch: you trade an integrated appliance for config-as-code and a bit of assembly. This guide maps the A10 objects you have today onto HAProxy primitives.
Map the object model first
A10’s vocabulary translates cleanly, so build a mapping table before touching config:
- A10
slb virtual-serverplus itsportbecomes an HAProxyfrontend(one bind per VIP:port). - A10
slb service-groupbecomes abackendwith itsserverlines. - A10
health monitorbecomes an HAProxyoption httpchkor a customcheck. - A10 source-IP or cookie persistence becomes
stick-tableplusstick on. - aFleX scripts become ACLs,
http-requestrules, andmapfiles.
Write this mapping down as a table before you touch any config. It becomes the checklist you validate against during cutover, and it surfaces the objects that do not map one to one, an aFleX script doing something procedural, a monitor with an unusual expect string, a persistence template with a short timeout, early enough to plan for rather than discover mid-migration. Include the scheduling method per service-group too, since A10 load-balancing methods map to HAProxy balance algorithms and a silent default change alters how traffic spreads.
A minimal frontend/backend looks like this:
frontend vs_web
bind 203.0.113.10:443 ssl crt /etc/haproxy/certs/web.pem
default_backend sg_web
backend sg_web
balance roundrobin
option httpchk GET /healthz
http-check expect status 200
server web1 10.0.1.11:8080 check inter 2s fall 3 rise 2
server web2 10.0.1.12:8080 check inter 2s fall 3 rise 2
Translate health monitors and persistence
A10 health monitors with expect strings map directly. An HTTP monitor matching a response body becomes http-check expect string OK. For raw TCP groups, drop the option httpchk and let the default connect check stand. Set inter, fall, and rise to match your A10 retry and interval values so failover timing does not surprise you.
Persistence is where people get tripped up. A10 source-IP persistence maps to a stick-table keyed on src:
backend sg_app
stick-table type ip size 200k expire 30m
stick on src
server app1 10.0.2.11:443 check
server app2 10.0.2.12:443 check
Cookie-based session affinity becomes cookie SRV insert indirect nocache with a cookie value per server line. That is genuinely cleaner than A10’s persist templates once you see it.
Move aFleX logic to ACLs and maps
aFleX is A10’s TCL scripting hook, and most real-world aFleX does header inspection, URI routing, or redirects. None of that needs scripting in HAProxy. Host and path routing becomes ACLs:
acl is_api hdr(host) -i api.example.com
acl is_static path_beg /static/
use_backend sg_api if is_api
use_backend sg_static if is_static
For large lookup tables (geo routing, customer-to-pool mapping), use a map file with req.hdr(host),map(/etc/haproxy/hosts.map) instead of a giant if/else chain. Reserve Lua for the rare cases that actually need procedural logic.
SSL/TLS and observability
Concatenate cert plus key plus chain into a single PEM and point crt at it, or use a crt directory for SNI. Tune ssl-default-bind-ciphers and set ssl-default-bind-options ssl-min-ver TLSv1.2. For metrics, enable the runtime stats socket and run the official Prometheus exporter on /metrics, then alert on backend up count and 5xx rate.
Rebuilding VRRP with keepalived
A10 gives you VRRP in the box. Rebuild it with keepalived: two HAProxy nodes, a shared virtual IP, and a vrrp_script that checks pidof haproxy so the VIP moves when the process dies, not just when the box does. Test the failover paths you actually care about before go-live: kill the HAProxy process, pull a network link, and reboot a node, confirming each time that the VIP relocates cleanly and that connections drain the way they did on the A10 pair. HA that has never been exercised is not HA.
Cut over one VIP at a time
Resist the urge to flip everything at once. Recreate a single virtual server as a frontend and backend, complete with its checks, TLS, and persistence, and run it in parallel with the A10 still serving production. Point a test hostname or a small, controlled slice of traffic at the HAProxy VIP and compare behaviour directly, watching health-check accuracy, persistence, and TLS/SNI resolution against what the A10 does for the same requests.
When a service matches, cut its VIP over by lowering the DNS TTL ahead of the change so clients pick up the new target quickly, then swing DNS or move the IP in a planned window while watching the stats page. Keep the A10 configured and ready so rollback is a single DNS or IP move rather than a rebuild. Migrate VIP by VIP, and deliberately leave the services that lean on A10-specific capabilities, hardware SSL offload at extreme rates, integrated WAF, GSLB, until their replacements are proven. That ordering keeps the risky pieces last, when you have the most operational confidence.
Where A10 still wins
Be honest about this. Keep A10 when you need hardware SSL offload at very high connection rates, where the dedicated crypto silicon beats commodity CPU. Keep it for integrated WAF and DDoS mitigation, GSLB across sites, and a vendor TAC to call at 3am. HAProxy can do most of that with extra components, but you are now the integrator and the support line.
The short version
A10 Thunder to HAProxy is a methodical rebuild, not a converter run. You map virtual servers to frontends, service groups to backends, monitors to checks, and aFleX to ACLs and maps, then layer keepalived for HA. The payoff is eliminated per-instance licensing and config that lives in Git. Model the license savings against the engineering and HA-hardware time in the calculator above before you commit a cutover date.