9.2 Application Load Balancer: HTTP/HTTPS Routing, Rules, and Conditions
Right, so you’ve got an Application Load Balancer (ALB). It’s not just a dumb traffic cop; it’s a reasonably sophisticated reverse proxy that can make decisions based on what’s inside the HTTP request. This is where you go from “please send this to a server” to “please send this specific kind of request to this specific group of servers.” The magic that makes this happen is a combination of Listeners, Rules, Conditions, and Actions. Let’s break it down without the marketing fluff.
The Listener: Your ALB’s Ears
First, your ALB needs to hear the request. That’s what a Listener does. It’s configured for a specific port (like 80 for HTTP or 443 for HTTPS) and just sits there, waiting. Think of it as the bouncer standing at a specific door of a nightclub. His only job is to check the invite (the incoming request on his assigned port) and then hand it off to the rules engine to figure out which VIP section it gets into.
You define this in Terraform or CloudFormation because clicking buttons in the AWS console for this is a special kind of madness. Here’s the Terraform for a basic HTTPS listener:
resource "aws_lb_listener" "main_https" {
load_balancer_arn = aws_lb.main.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = aws_acm_certificate.my_domain.arn
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "No route found. The ALB is listening, but no rules matched."
status_code = "404"
}
}
}
Notice the default_action. This is crucial. This is what happens if a request comes in that doesn’t match any of your custom routing rules. It’s your catch-all, your safety net. Making it a fixed-response 404 is a fantastic practice because it means you’re not accidentally sending traffic to some random backend just because you misconfigured a rule.
Rules: The “If This, Then That” of Traffic
Rules are where the real work happens. They are evaluated in order, from lowest priority number to highest (which, counter-intuitively, means priority 1 is evaluated first). The first rule whose conditions are all met wins, and its action is executed. This is why you often see a last, catch-all rule that just forwards to a default target group—it has the highest priority number, so it’s checked last.
Each rule has one or more conditions and one or more actions.
Conditions: How to Pick Your Target
Conditions are the “if” part of your rule. The ALB can look at various parts of the HTTP request. The most common and useful ones are:
host-header: The domain name the client is asking for (e.g.,api.example.com,static.example.com). This is your bread and butter for routing based on subdomains.path-pattern: The URL path (e.g.,/api/*,/static/*). Essential for path-based routing.http-header: The value of a specific HTTP header (e.g.,X-API-Version: 2023-07-01). Great for canary launches or versioning.http-request-method: The HTTP verb (e.g.,GET,POST). Useful for sending writes and reads to different backends.
Here’s the beautiful part: you can combine conditions within a single rule. The rule only matches if ALL of its conditions are true (it’s a logical AND). So a rule with host-header: api.example.com AND path-pattern: /v2/* will only catch requests for that specific subdomain and that specific path prefix.
Want a logical OR? You have to create two separate rules. It’s a bit clunky, but that’s how the cookie crumbles.
Actions: What to Actually Do
Once a rule matches, you tell the ALB what action to take. The most common action is forward, which sends the request to a specified target group. But there are others, like redirect (incredibly useful for forcing HTTP to HTTPS) and fixed-response (great for maintenance pages or quickly mocking an endpoint).
Here’s a Terraform example that puts it all together, routing traffic to different target groups based on the path:
resource "aws_lb_listener_rule" "api" {
listener_arn = aws_lb_listener.main_https.arn
priority = 100 # A nice high number, evaluated late
action {
type = "forward"
target_group_arn = aws_lb_target_group.api.arn
}
condition {
path_pattern {
values = ["/api/*"]
}
}
}
resource "aws_lb_listener_rule" "static_assets" {
listener_arn = aws_lb_listener.main_https.arn
priority = 200 # Evaluated after the API rule
action {
type = "forward"
target_group_arn = aws_lb_target_group.static.arn
}
condition {
path_pattern {
values = ["/static/*"]
}
}
condition {
host_header {
values = ["app.example.com"] # Combine with a host condition
}
}
}
The Gotchas and Grey Matter
Priority Numbers are a Landmine: They are not auto-assigned. If you define a rule with priority 100 and then later add another rule that should be evaluated before it, you must manually assign a free priority number (e.g., 99). This is a fantastic way to create routing loops or unexpected behavior. Use a tool like Terraform that can manage this for you, or you will eventually weep.
The Default Action is Not a Rule: Remember that
default_actionon the listener? It exists outside the priority-based rules system. It only executes if no rules match. It’s your last line of defense.Path Patterns are Greedy, But Not That Greedy: The path pattern
/img/*will match/img/cat.jpgand/img/2023/07/cat.jpg. But it won’t match just/img. For that, you’d need a separate pattern like/imgor/img*. This trips everyone up.Stickiness is at the Target Group Level: If you need session affinity (stickiness), you configure it on the target group, not the rule. So if you have multiple rules forwarding to the same target group, they’ll all share the same stickiness behavior. Plan accordingly.
The ALB’s routing is powerful, but with great power comes the great responsibility of not screwing up your priority order. Think of it like a complex if/else if/else statement in your code. You want your most specific conditions evaluated first and your most general ones last. Get that right, and your ALB will be a seamless traffic director. Get it wrong, and it’ll feel like it’s actively working against you. Because, in a way, it is.