DevOpsil

HAProxy ACL-Based Routing For Blue-Green Deployments Without Downtime

Majid Iqbal NayyarMajid Iqbal Nayyar5 min read

HAProxy ACL-Based Routing for Blue-Green Deployments Without Downtime

Blue-green deployments are one of the cleanest ways to ship without waking anyone up at 3am. HAProxy makes this surprisingly straightforward with ACL-based routing — no service mesh required, no Kubernetes magic needed.

Here's your quick reference for wiring it up.


The Core Concept

You run two identical environments — blue (current production) and green (new version). HAProxy sits in front and decides which backend gets traffic. You shift the needle gradually or all at once, validate, then decommission the old environment.

Client → HAProxy → [Blue Backend]  (current)
                 → [Green Backend] (new version)

Minimal HAProxy Config Structure

global
    log /dev/log local0
    maxconn 50000
    daemon

defaults
    mode http
    timeout connect 5s
    timeout client  30s
    timeout server  30s
    option httplog
    option forwardfor
    option http-server-close

frontend web_frontend
    bind *:80
    bind *:443 ssl crt /etc/ssl/certs/your-cert.pem

    # ACL definitions go here
    # Backend routing goes here

backend blue
    server blue-01 10.0.1.10:8080 check
    server blue-02 10.0.1.11:8080 check

backend green
    server green-01 10.0.2.10:8080 check
    server green-02 10.0.2.11:8080 check

ACL Routing Patterns

1. Route by Header (Canary Testing)

Send internal testers to green while everyone else hits blue:

frontend web_frontend
    bind *:80

    acl is_canary hdr(X-Canary) -i true
    acl is_internal src 10.0.0.0/8

    use_backend green if is_canary
    use_backend green if is_internal
    default_backend blue

Persist users to green once they're assigned — avoids session confusion:

frontend web_frontend
    bind *:80

    acl on_green hdr_sub(Cookie) "deployment=green"

    use_backend green if on_green
    default_backend blue

Your app (or HAProxy itself) sets Set-Cookie: deployment=green for the canary cohort.

3. Percentage-Based Traffic Split

Use nbsrv and weight tricks, or the cleaner random approach:

frontend web_frontend
    bind *:80

    # Send ~10% to green using random balancing
    use_backend green if { rand(10) eq 0 }
    default_backend blue

For more precise control, use backend weights:

backend traffic_split
    balance roundrobin
    server blue-01 10.0.1.10:8080 check weight 90
    server green-01 10.0.2.10:8080 check weight 10

4. Route by URL Path

Useful when you're migrating specific endpoints first:

frontend web_frontend
    bind *:80

    acl is_api path_beg /api/v2
    acl is_new_dashboard path_beg /dashboard/new

    use_backend green if is_api
    use_backend green if is_new_dashboard
    default_backend blue

5. Route by Query Parameter

frontend web_frontend
    bind *:80

    acl wants_green urlp(version) green

    use_backend green if wants_green
    default_backend blue

Hit https://yourapp.com/feature?version=green to force green routing.


Zero-Downtime Cutover via Runtime API

HAProxy's Runtime API lets you shift traffic without reloading config:

# Connect to the HAProxy stats socket
echo "show info" | socat stdio /var/run/haproxy/admin.sock

# Drain blue servers (removes from active rotation gracefully)
echo "set server blue/blue-01 state drain" | socat stdio /var/run/haproxy/admin.sock
echo "set server blue/blue-02 state drain" | socat stdio /var/run/haproxy/admin.sock

# Bring green servers to ready state
echo "set server green/green-01 state ready" | socat stdio /var/run/haproxy/admin.sock
echo "set server green/green-02 state ready" | socat stdio /var/run/haproxy/admin.sock

# If you need to roll back immediately
echo "set server blue/blue-01 state ready" | socat stdio /var/run/haproxy/admin.sock
echo "set server green/green-01 state drain" | socat stdio /var/run/haproxy/admin.sock

drain state lets existing connections finish — new connections stop going to that server. Clean, no dropped requests.


Health Check Configuration

Don't route to green until it's actually ready:

backend green
    option httpchk GET /health HTTP/1.1\r\nHost:\ yourapp.com
    http-check expect status 200
    default-server inter 2s fall 3 rise 2
    server green-01 10.0.2.10:8080 check
    server green-02 10.0.2.11:8080 check
  • inter 2s — check every 2 seconds
  • fall 3 — mark down after 3 consecutive failures
  • rise 2 — mark up after 2 consecutive successes

Graceful Config Reload (When You Must)

If you do need to reload config (ACL changes, new server additions):

# Validate config first — always
haproxy -c -f /etc/haproxy/haproxy.cfg

# Graceful reload — zero dropped connections
systemctl reload haproxy

# Or the manual way
haproxy -f /etc/haproxy/haproxy.cfg -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid)

The -sf flag sends a SIGUSR1 to old processes, telling them to finish current connections and exit.


Quick Rollback Checklist

StepCommand
Drain greenset server green/green-01 state drain
Restore blueset server blue/blue-01 state ready
Verify trafficecho "show stat" | socat stdio /var/run/haproxy/admin.sock
Check error ratesTail your access log: tail -f /var/log/haproxy.log

Key Takeaways

  • Use drain over maint — drain finishes existing connections, maint kills them
  • Test your ACLs with haproxy -c before every reload
  • Health checks are non-negotiable — never route to a server that hasn't passed at least rise 2 checks
  • Start with header/cookie routing for your first blue-green — it's the most controllable

The Runtime API is your best friend here. Master socat + the admin socket and you can shift traffic in milliseconds without touching a config file.

Share:

Was this article helpful?

Majid Iqbal Nayyar
Majid Iqbal Nayyar

Data Infrastructure Engineer

Your data is only as good as the infrastructure it sits on. I specialize in PostgreSQL, Redis, database migrations, backup strategies, and making sure your data survives whatever chaos your application throws at it.

Related Articles

Discussion