This episode uses HAProxy as an API gateway: reverse proxy for REST and gRPC, path rewriting, virtual hosts, authentication integration with JWT and external auth, and routing patterns for microservices with service discovery.

More and more architectures are shaped like microservices, and every service needs a single entry point. HAProxy is a great fit for the API gateway role: receiving all requests, determining the destination service, rewriting, and checking identity.
Episode 11 combines everything you've learned — routing, ACLs, rewriting, and protection — into one door for REST and gRPC, complete with authentication integration and service discovery patterns.
The big picture: a single domain api.example.com that hosts many services, separated by path prefixes:
frontend api_front
bind *:443 ssl crt /etc/haproxy/certs/fullchain.pem
mode http
option httplog
acl svc_users path_beg /users
acl svc_orders path_beg /orders
acl svc_payment path_beg /payment
use_backend users_back if svc_users
use_backend orders_back if svc_orders
use_backend payment_back if svc_payment
default_backend users_backEach service gets its own path segment. acl svc_users path_beg /users routes requests starting with /users to the users_back backend.
Backends often don't want to accept the gateway prefix. Strip the prefix before forwarding:
backend users_back
balance roundrobin
option httpchk GET /healthz
http-request set-path %[path,regsub(^/users/,/)]
server user1 10.0.0.11:8080 check
server user2 10.0.0.12:8080 checkhttp-request set-path %[path,regsub(^/users/,/)] replaces the /users/ prefix with / so the backend receives the service's real path. This technique keeps backends focused on their business domain.
gRPC runs on top of HTTP/2 with a special content-type header:
frontend grpc_front
bind *:443 ssl crt /etc/haproxy/certs/fullchain.pem \
alpn h2,http/1.1
mode http
acl is_grpc req.hdr(Content-Type) -i application/grpc
acl svc_catalog path_beg /catalog.CatalogService/
use_backend grpc_catalog if is_grpc svc_catalog
default_backend grpc_defaultacl is_grpc req.hdr(Content-Type) -i application/grpc detects gRPC traffic from the Content-Type header, and use_backend grpc_catalog if is_grpc svc_catalog routes specific RPC calls to the right backend.
The simplest pattern: HAProxy doesn't validate the token, it just forwards the Authorization header:
frontend api_front
bind *:443 ssl crt /etc/haproxy/certs/fullchain.pem
mode http
acl has_token req.hdr(Authorization) -m found
http-request set-header X-User-ID \
%[req.hdr(Authorization),jwt_payload('sub')]
http-request deny deny_status 401 if !has_token
default_backend users_backThe %[req.hdr(Authorization),jwt_payload('sub')] directive extracts the sub claim from the JWT and puts it into the X-User-ID header. This way the backend doesn't need to parse the token again to recognize the user.
For centralized authentication, HAProxy can call an auth service before forwarding the request:
backend auth_check
server auth 10.0.0.100:8443 ssl verify required \
ca-file /etc/ssl/certs/ca-certificates.crt
frontend api_front
bind *:443 ssl crt /etc/haproxy/certs/fullchain.pem
mode http
http-request lua.auth
http-request deny deny_status 401 if !{ var(txn.authed) -m bool }
default_backend users_backThe example above uses Lua to call an auth endpoint, then var(txn.authed) stores the result. If the result isn't true, the request is rejected with 401 before reaching the backend.
Some practices when forwarding identity:
Backend servers aren't always static. HAProxy can resolve server names from DNS and follow changes:
backend users_back
balance roundrobin
server svc users-svc.internal:8080 check resolvers dns_srv \
init-addr libc,none
resolver dns_srv nameserver dns1 10.0.0.53:53The server svc users-svc.internal:8080 check resolvers dns_srv directive makes HAProxy follow the IP from DNS at set intervals. When service discovery updates DNS, HAProxy follows automatically without a reload.
A healthy API gateway pattern:
curl -s -o /dev/null -w "%{http_code}\n" https://api.example.com/users/me -k
curl -s -o /dev/null -w "%{http_code}\n" https://api.example.com/orders/1 -kcurl -s -o /dev/null -w "%{http_code}\n" only prints the status; compare results for different paths to make sure each one reaches the correct backend.
Episode 11 sums up HAProxy's role as an API gateway: one door for many services, rewriting that keeps backends clean, centralized authentication, and service discovery that stays alive through DNS.
Key takeaways:
use_backend.set-path with regsub strips the gateway prefix at the backend.In the next episode we'll cover layer 4 proxy & TCP routing — the difference between TCP and HTTP modes, proxying raw services like databases and gRPC, and SNI inspection and TLS passthrough.