Right, let’s talk about target groups. This is where the ELB rubber meets the road. You’ve told your load balancer to distribute traffic, but you haven’t told it where to send it. That’s the target group’s job. It’s a logical grouping of your backend endpoints—your poor, overworked servers (or functions) that will actually do the heavy lifting.

Think of it like a bouncer at an exclusive club. The ELB is the door, checking IDs (health checks). The target group is the bouncer’s list: “Okay, you’re on the list, you can come in. You? Not on the list. Get lost.” You need to define what “the list” looks like.

The Four Flavors of Backend

You have four choices for what goes on that list, and picking the right one is non-negotiable. AWS, in its infinite wisdom, decided these should be mutually exclusive within a single target group. You can’t mix and match instance and IP targets in the same group. Frankly, that’s a good thing; it keeps us from building horrifying, un-debuggable contraptions.

  • Instance: The classic. You register an EC2 instance by its ID. The load balancer then routes traffic to the primary private IP address of that instance. Simple, effective, and the default for a reason. Use this when your backend is a fleet of EC2 instances, probably managed by an Auto Scaling group (which, by the way, integrates with target groups seamlessly).

  • IP: This is where you get specific. You register a specific IP address, like the private IP of a network interface on an EC2 instance, or an IP from an on-premises server you’ve connected via Direct Connect or VPN. This is your go-to for hybrid architectures. Heads up: The IP must be reachable from the VPC. Don’t try to put your home router’s IP in here; it will laugh at you and then fail the health check.

  • Lambda: This one’s weird and wonderful. You don’t register a server; you register a Lambda function. The Application Load Balancer becomes a super-easy way to invoke your serverless functions directly via an HTTP request. No API Gateway configuration needed. The ALB transforms the HTTP request into a JSON payload for Lambda, and then transforms Lambda’s JSON response back into HTTP. Magic.

  • ALB: Yes, you read that right. You can have a target group that points to… another Application Load Balancer. This is for those deep, nested architectures where you need to do chained routing. It’s a powerful pattern, but it’s also a great way to confuse yourself at 2 a.m. during an outage. Tread carefully.

Health Checks: Your Canary in the Coal Mine

This is the most important part, and where most people mess up. A target group doesn’t just blindly send traffic to its members. It constantly asks them, “You alive?” via a health check. You configure the path, the port, the protocol (HTTP/HTTPS), and the timing.

If a target fails too many times, the ELB stops sending traffic to it. When it starts passing again, traffic resumes. This is your primary line of defense against a failing instance taking down your app.

The default health check is to the root path / on the target’s port. This is almost always wrong. You need a dedicated, lightweight health check endpoint that checks critical internal state (e.g., can you talk to the database?) without running your entire application stack.

# A CloudFormation example showing a robust health check configuration
TargetGroup:
  Type: AWS::ElasticLoadBalancingV2::TargetGroup
  Properties:
    HealthCheckIntervalSeconds: 30
    HealthCheckPath: /api/health
    HealthCheckProtocol: HTTP
    HealthCheckTimeoutSeconds: 5
    HealthyThresholdCount: 2
    UnhealthyThresholdCount: 3
    Matcher:
      HttpCode: 200-299
    Port: 8080
    Protocol: HTTP
    TargetType: instance
    VpcId: !Ref VpcId

See that Matcher? It’s not enough for the endpoint to return a response; it has to return a successful one (by default, 200). You can define a range of acceptable codes. The thresholds (2 and 3) mean it takes two consecutive successes to mark a target healthy, but three consecutive failures to mark it unhealthy. This provides a little hysteresis to prevent flapping.

The Port Override Party Trick

Here’s a gem that’s saved my bacon more than once. When you register a target by instance ID, traffic is sent to the port on which the target group is listening. But what if you have a single instance running multiple services on different ports?

You can override this on a per-target basis. It’s a bit hidden in the UI, but it’s there in the API/CLI. This lets you have a target group listening on port 80, but register an instance and tell the ELB to actually send traffic to port 3000 on that specific instance.

# AWS CLI example: Register an instance but override the port it receives traffic on
aws elbv2 register-targets \
  --target-group-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-tg/1234567890abcdef \
  --targets Id=i-1234567890abcdef0,Port=3000

The Lambda Quirk

Lambda targets have one big “gotcha”: they are, by design, synchronous. The ALB waits for your function to finish its execution and return a response. This means you are bound by Lambda’s execution timeout (15 minutes). But for the love of all that is holy, if you’re using an ALB to invoke a Lambda that runs for more than a minute, you’re probably doing something wrong and should rethink your architecture. The ALB itself has an idle timeout of a few minutes, and users (and their browsers) will have given up long before your function finishes.

The payload structure is also specific. You need to ensure your Lambda function is structured to handle the ALB’s specific JSON event format and returns a response in the exact structure it expects.

# A Lambda function designed to handle an ALB request
import json

def lambda_handler(event, context):
    # Your amazing logic here
    body = json.dumps({"message": "Hello from Lambda!"})
    
    # You MUST return a response in this exact format for the ALB to understand it.
    return {
        "statusCode": 200,
        "statusDescription": "200 OK",
        "isBase64Encoded": False,
        "headers": {"Content-Type": "application/json"},
        "body": body
    }

The bottom line? Your target group is the brain of your load balancer. Configure it thoughtfully, especially the health checks. Get it wrong, and you’ll either be sending traffic to dead instances or taking perfectly healthy ones out of rotation for no reason. And nobody wants that.