HAProxy doesn’t actually "allow" or "block" IP addresses; it selectively forwards or drops traffic based on rules you define, acting as a gatekeeper rather than a bouncer.
Let’s see it in action. Imagine you have a web server behind HAProxy and you want to allow traffic only from your internal network (192.168.1.0/24) and a specific partner IP (10.0.0.5), while blocking everything else.
Here’s how you’d configure HAProxy to achieve this:
frontend http_in
bind *:80
acl allowed_networks src 192.168.1.0/24
acl partner_ip src 10.0.0.5
http-request deny if !allowed_networks !partner_ip
default_backend web_servers
In this configuration:
frontend http_in: Defines the entry point for incoming HTTP traffic.bind *:80: Tells HAProxy to listen on port 80 for incoming connections.acl allowed_networks src 192.168.1.0/24: This creates an Access Control List (ACL) namedallowed_networks. It matches any source IP address that falls within the192.168.1.0/24subnet.acl partner_ip src 10.0.0.5: Another ACL namedpartner_ip, matching the specific source IP address10.0.0.5.http-request deny if !allowed_networks !partner_ip: This is the core of the logic. It says: "If the source IP address does NOT matchallowed_networksAND it also does NOT matchpartner_ip, then deny the request." The!operator negates the ACL match.default_backend web_servers: If a request isn’t denied by the previous rule, it’s forwarded to the backend namedweb_servers.
This setup effectively creates a whitelist. Any IP address not explicitly listed in the allowed_networks or partner_ip ACLs will have its HTTP request denied by HAProxy before it even reaches your backend web servers.
The mental model here is that HAProxy evaluates rules sequentially. When a request comes in, it checks the acl definitions. If an acl condition is met, it’s "true" for that request. Then, the http-request deny if statement acts as a conditional gate. If the combined conditions after if evaluate to true (meaning the source IP is neither in the allowed network nor the partner IP), the deny action is executed. Otherwise, the request continues down the configuration, eventually being passed to the default_backend.
You can also use these ACLs to allow specific traffic. For instance, to only allow access from your internal network and a partner, you would do something like this:
frontend http_in
bind *:80
acl internal_net src 192.168.1.0/24
acl partner_ip src 10.0.0.5
http-request allow if internal_net
http-request allow if partner_ip
http-request deny
default_backend web_servers
Here, the http-request allow if internal_net and http-request allow if partner_ip lines explicitly permit traffic from those sources. The final http-request deny acts as a catch-all for anything that didn’t match the preceding allow rules.
A common pitfall is forgetting that HAProxy evaluates rules in the order they appear. If you have a broad allow rule before a specific deny rule that you intended to block something, the deny rule might never be reached for certain traffic. The http-request directives, like allow and deny, are executed in order until one matches. If an allow matches, the request is allowed and processing stops for that request’s HTTP request rules. If a deny matches, the request is denied and processing stops. If neither matches, processing continues to the next http-request rule.
The http-request deny directive, when placed at the end of a series of allow rules, functions as a default block. It ensures that any traffic not explicitly permitted by the preceding rules is denied. This is a fundamental pattern for creating secure access policies.
You can also combine multiple source IP conditions within a single ACL using logical operators. For example, to allow traffic from either your internal network OR a partner IP, you could write:
frontend http_in
bind *:80
acl allowed_sources src 192.168.1.0/24 10.0.0.5
http-request deny if !allowed_sources
default_backend web_servers
Here, allowed_sources will be true if the source IP matches any of the IPs/subnets listed. The http-request deny if !allowed_sources then blocks everything not in that combined list.
When dealing with large lists of IPs, you can also use ACL files for better organization and management.
frontend http_in
bind *:80
acl allowed_ips src -f /etc/haproxy/allowed_ips.lst
http-request deny if !allowed_ips
default_backend web_servers
And the /etc/haproxy/allowed_ips.lst file would contain:
192.168.1.10
192.168.1.11
10.0.0.5
Each IP address or subnet is on a new line. This is particularly useful for dynamic IP lists or when you have many specific IPs to manage.
You can also perform actions based on the presence of a specific header, like an X-Forwarded-For header, which is often used by upstream proxies or load balancers to indicate the original client IP. However, relying solely on X-Forwarded-For without proper validation can be a security risk, as it can be spoofed. HAProxy’s src keyword always refers to the IP address of the direct connecting client.
The next concept to explore is how to implement rate limiting based on source IP addresses to prevent DoS attacks.