When working with HAproxy 1.6 as a load balancer, you might encounter scenarios where you need to dynamically add response headers based on the incoming request URI. A common use case is adding cache-control headers for specific API endpoints while leaving other routes unaffected.
The initial approach many try is using ACLs with path matching for response headers:
acl api path_reg ^/api/(.*)$
http-response add-header Cache-Control public,max-age="600" if api
However, HAproxy 1.6 will reject this with the warning:
[WARNING] acl 'api' will never match because it only involves keywords
that are incompatible with 'backend http-response header rule'
Here's an effective method that works with HAproxy 1.6:
frontend http-in
bind *:80
acl is_api path_beg /api
reqrep ^([^\ ]*\ /api) \1 if is_api
use_backend api_servers if is_api
default_backend other_servers
backend api_servers
server api1 10.0.0.1:8080 check
http-response set-header Cache-Control "public, max-age=600"
backend other_servers
server app1 10.0.0.2:8080 check
For more complex URI patterns, consider this method:
frontend http-in
bind *:80
acl is_api path_beg /api
capture request header Host len 128
capture request header X-Forwarded-For len 128
use_backend api_servers if is_api
default_backend other_servers
backend api_servers
server api1 10.0.0.1:8080 check
http-response set-header Cache-Control "public, max-age=600"
http-response set-header Vary "Accept-Encoding"
When implementing these solutions:
- Test header modifications thoroughly in staging before production
- Be aware of HAproxy's processing order for requests and responses
- Consider upgrading to newer HAproxy versions for more flexible header manipulation
- Monitor performance impact when adding multiple conditional headers
Here's a complete example for a production-like scenario:
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
frontend http-in
bind *:80
bind *:443 ssl crt /etc/ssl/certs/example.pem
redirect scheme https if !{ ssl_fc }
acl is_static path_beg /static/
acl is_api path_beg /api/
use_backend static_servers if is_static
use_backend api_servers if is_api
default_backend app_servers
backend static_servers
balance roundrobin
option forwardfor
http-response set-header Cache-Control "public, max-age=31536000"
server static1 10.0.0.10:80 check
server static2 10.0.0.11:80 check
backend api_servers
balance roundrobin
option forwardfor
http-response set-header Cache-Control "public, max-age=600"
http-response set-header X-API-Version "1.0"
server api1 10.0.0.20:8080 check
server api2 10.0.0.21:8080 check
backend app_servers
balance roundrobin
option forwardfor
server app1 10.0.0.30:8080 check
server app2 10.0.0.31:8080 check
When working with HAProxy 1.6 as a load balancer for Tomcat servers, a common requirement is to dynamically set response headers based on the incoming request URI. The specific case we're addressing involves adding a Cache-Control
header for /api
endpoints while excluding other paths.
The initial approach using path-based ACLs with http-response
fails because:
acl api path_reg ^/api/(.*)$
http-response add-header Cache-Control public,max-age=\"600\" if api
This triggers a warning because path_reg
fetch method isn't compatible with response-time processing in HAProxy 1.6.
Here's the proper way to implement this in HAProxy 1.6:
frontend http-in
bind *:80
# Capture the request URI
capture request header Host len 128
capture request header User-Agent len 128
capture request uri len 128
# Define ACL based on captured URI
acl is_api_path capture.req.uri -m beg /api/
# Set response header conditionally
http-response set-header Cache-Control \"public, max-age=600\" if is_api_path
default_backend tomcat_servers
For more complex matching patterns, you can use transaction variables:
frontend http-in
bind *:80
# Store URI in transaction variable
http-request set-var(txn.request_uri) path
# Define ACL using the variable
acl is_api_path var(txn.request_uri) -m beg /api/
# Set response header
http-response set-header Cache-Control \"public, max-age=600\" if is_api_path
default_backend tomcat_servers
When implementing this solution, keep in mind:
- The capture method has slightly better performance than transaction variables
- For simple prefix matches (
/api/
), use-m beg
instead of regex - In HAProxy 2.0+, you can use
path
directly withhttp-response
After implementing the changes:
- Test syntax with:
haproxy -f /etc/haproxy/haproxy.cfg -c
- Verify with curl:
curl -I http://yourdomain.com/api/test
- Check for the
Cache-Control
header in responses