Configure AWS Load Balancer Target Groups

The aws:alb/targetGroup:TargetGroup resource, part of the Pulumi AWS provider, defines a target group that routes load balancer traffic to registered targets: EC2 instances, IP addresses, Lambda functions, or other load balancers. This guide focuses on three capabilities: target type selection, protocol configuration, and connection termination behavior.

Target groups require a VPC (except for Lambda targets) and connect to load balancers through listener rules. The examples are intentionally small. Combine them with your own load balancer, health checks, and target registration.

Route traffic to EC2 instances by ID

Most deployments route HTTP traffic to EC2 instances, where the target group tracks instances by ID and forwards to a specific port.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const main = new aws.ec2.Vpc("main", {cidrBlock: "10.0.0.0/16"});
const test = new aws.lb.TargetGroup("test", {
    name: "tf-example-lb-tg",
    port: 80,
    protocol: "HTTP",
    vpcId: main.id,
});
import pulumi
import pulumi_aws as aws

main = aws.ec2.Vpc("main", cidr_block="10.0.0.0/16")
test = aws.lb.TargetGroup("test",
    name="tf-example-lb-tg",
    port=80,
    protocol="HTTP",
    vpc_id=main.id)
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lb"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		main, err := ec2.NewVpc(ctx, "main", &ec2.VpcArgs{
			CidrBlock: pulumi.String("10.0.0.0/16"),
		})
		if err != nil {
			return err
		}
		_, err = lb.NewTargetGroup(ctx, "test", &lb.TargetGroupArgs{
			Name:     pulumi.String("tf-example-lb-tg"),
			Port:     pulumi.Int(80),
			Protocol: pulumi.String("HTTP"),
			VpcId:    main.ID(),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var main = new Aws.Ec2.Vpc("main", new()
    {
        CidrBlock = "10.0.0.0/16",
    });

    var test = new Aws.LB.TargetGroup("test", new()
    {
        Name = "tf-example-lb-tg",
        Port = 80,
        Protocol = "HTTP",
        VpcId = main.Id,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.lb.TargetGroup;
import com.pulumi.aws.lb.TargetGroupArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var main = new Vpc("main", VpcArgs.builder()
            .cidrBlock("10.0.0.0/16")
            .build());

        var test = new TargetGroup("test", TargetGroupArgs.builder()
            .name("tf-example-lb-tg")
            .port(80)
            .protocol("HTTP")
            .vpcId(main.id())
            .build());

    }
}
resources:
  test:
    type: aws:lb:TargetGroup
    properties:
      name: tf-example-lb-tg
      port: 80
      protocol: HTTP
      vpcId: ${main.id}
  main:
    type: aws:ec2:Vpc
    properties:
      cidrBlock: 10.0.0.0/16

The targetType defaults to “instance”, meaning you register EC2 instance IDs as targets. The port and protocol define where traffic is forwarded. The vpcId places the target group in your VPC, enabling instance discovery.

Route traffic to specific IP addresses

Container workloads and on-premises services often need IP-based routing rather than instance IDs.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const main = new aws.ec2.Vpc("main", {cidrBlock: "10.0.0.0/16"});
const ip_example = new aws.lb.TargetGroup("ip-example", {
    name: "tf-example-lb-tg",
    port: 80,
    protocol: "HTTP",
    targetType: "ip",
    vpcId: main.id,
});
import pulumi
import pulumi_aws as aws

main = aws.ec2.Vpc("main", cidr_block="10.0.0.0/16")
ip_example = aws.lb.TargetGroup("ip-example",
    name="tf-example-lb-tg",
    port=80,
    protocol="HTTP",
    target_type="ip",
    vpc_id=main.id)
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lb"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		main, err := ec2.NewVpc(ctx, "main", &ec2.VpcArgs{
			CidrBlock: pulumi.String("10.0.0.0/16"),
		})
		if err != nil {
			return err
		}
		_, err = lb.NewTargetGroup(ctx, "ip-example", &lb.TargetGroupArgs{
			Name:       pulumi.String("tf-example-lb-tg"),
			Port:       pulumi.Int(80),
			Protocol:   pulumi.String("HTTP"),
			TargetType: pulumi.String("ip"),
			VpcId:      main.ID(),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var main = new Aws.Ec2.Vpc("main", new()
    {
        CidrBlock = "10.0.0.0/16",
    });

    var ip_example = new Aws.LB.TargetGroup("ip-example", new()
    {
        Name = "tf-example-lb-tg",
        Port = 80,
        Protocol = "HTTP",
        TargetType = "ip",
        VpcId = main.Id,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.lb.TargetGroup;
import com.pulumi.aws.lb.TargetGroupArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var main = new Vpc("main", VpcArgs.builder()
            .cidrBlock("10.0.0.0/16")
            .build());

        var ip_example = new TargetGroup("ip-example", TargetGroupArgs.builder()
            .name("tf-example-lb-tg")
            .port(80)
            .protocol("HTTP")
            .targetType("ip")
            .vpcId(main.id())
            .build());

    }
}
resources:
  ip-example:
    type: aws:lb:TargetGroup
    properties:
      name: tf-example-lb-tg
      port: 80
      protocol: HTTP
      targetType: ip
      vpcId: ${main.id}
  main:
    type: aws:ec2:Vpc
    properties:
      cidrBlock: 10.0.0.0/16

Setting targetType to “ip” changes how you register targets: you provide IP addresses instead of instance IDs. IPs must come from VPC subnets or RFC 1918/6598 private ranges. This mode works well for ECS tasks, Kubernetes pods, or hybrid architectures.

Invoke Lambda functions from load balancer requests

Serverless applications can receive HTTP requests through a load balancer by routing to Lambda functions.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const lambda_example = new aws.lb.TargetGroup("lambda-example", {
    name: "tf-example-lb-tg",
    targetType: "lambda",
});
import pulumi
import pulumi_aws as aws

lambda_example = aws.lb.TargetGroup("lambda-example",
    name="tf-example-lb-tg",
    target_type="lambda")
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lb"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := lb.NewTargetGroup(ctx, "lambda-example", &lb.TargetGroupArgs{
			Name:       pulumi.String("tf-example-lb-tg"),
			TargetType: pulumi.String("lambda"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var lambda_example = new Aws.LB.TargetGroup("lambda-example", new()
    {
        Name = "tf-example-lb-tg",
        TargetType = "lambda",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.lb.TargetGroup;
import com.pulumi.aws.lb.TargetGroupArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var lambda_example = new TargetGroup("lambda-example", TargetGroupArgs.builder()
            .name("tf-example-lb-tg")
            .targetType("lambda")
            .build());

    }
}
resources:
  lambda-example:
    type: aws:lb:TargetGroup
    properties:
      name: tf-example-lb-tg
      targetType: lambda

When targetType is “lambda”, the target group invokes a Lambda function for each request. You don’t specify port or protocol because Lambda handles HTTP internally. The load balancer passes request details to the function and returns its response to the client.

Preserve connections to unhealthy targets

Some Network Load Balancer workloads maintain connections even when targets fail health checks, allowing graceful degradation.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const tcp_example = new aws.lb.TargetGroup("tcp-example", {
    name: "tf-example-lb-nlb-tg",
    port: 25,
    protocol: "TCP",
    vpcId: main.id,
    targetHealthStates: [{
        enableUnhealthyConnectionTermination: false,
    }],
});
import pulumi
import pulumi_aws as aws

tcp_example = aws.lb.TargetGroup("tcp-example",
    name="tf-example-lb-nlb-tg",
    port=25,
    protocol="TCP",
    vpc_id=main["id"],
    target_health_states=[{
        "enable_unhealthy_connection_termination": False,
    }])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lb"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := lb.NewTargetGroup(ctx, "tcp-example", &lb.TargetGroupArgs{
			Name:     pulumi.String("tf-example-lb-nlb-tg"),
			Port:     pulumi.Int(25),
			Protocol: pulumi.String("TCP"),
			VpcId:    pulumi.Any(main.Id),
			TargetHealthStates: lb.TargetGroupTargetHealthStateArray{
				&lb.TargetGroupTargetHealthStateArgs{
					EnableUnhealthyConnectionTermination: pulumi.Bool(false),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var tcp_example = new Aws.LB.TargetGroup("tcp-example", new()
    {
        Name = "tf-example-lb-nlb-tg",
        Port = 25,
        Protocol = "TCP",
        VpcId = main.Id,
        TargetHealthStates = new[]
        {
            new Aws.LB.Inputs.TargetGroupTargetHealthStateArgs
            {
                EnableUnhealthyConnectionTermination = false,
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.lb.TargetGroup;
import com.pulumi.aws.lb.TargetGroupArgs;
import com.pulumi.aws.lb.inputs.TargetGroupTargetHealthStateArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var tcp_example = new TargetGroup("tcp-example", TargetGroupArgs.builder()
            .name("tf-example-lb-nlb-tg")
            .port(25)
            .protocol("TCP")
            .vpcId(main.id())
            .targetHealthStates(TargetGroupTargetHealthStateArgs.builder()
                .enableUnhealthyConnectionTermination(false)
                .build())
            .build());

    }
}
resources:
  tcp-example:
    type: aws:lb:TargetGroup
    properties:
      name: tf-example-lb-nlb-tg
      port: 25
      protocol: TCP
      vpcId: ${main.id}
      targetHealthStates:
        - enableUnhealthyConnectionTermination: false

The targetHealthStates block controls connection termination behavior. Setting enableUnhealthyConnectionTermination to false keeps connections open when targets become unhealthy. This only applies to Network Load Balancers using TCP or TLS protocols.

Beyond these examples

These snippets focus on specific target group features: target type selection, protocol and port configuration, and connection termination behavior. They’re intentionally minimal rather than full load balancing solutions.

The examples reference pre-existing infrastructure such as VPCs for instance, IP, and ALB target groups, and load balancers to attach the target group to. They focus on target group configuration rather than provisioning the surrounding infrastructure.

To keep things focused, common target group patterns are omitted, including:

  • Health check configuration (healthCheck block)
  • Session stickiness (stickiness block)
  • Deregistration delay and connection draining
  • Load balancing algorithms (roundRobin, leastOutstandingRequests, weightedRandom)
  • Cross-zone load balancing settings

These omissions are intentional: the goal is to illustrate how each target group feature is wired, not provide drop-in load balancing modules. See the Target Group resource reference for all available configuration options.

Let's configure AWS Load Balancer Target Groups

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Target Types & Configuration
What target types are available?
You can choose from four target types: instance (EC2 instances), ip (IP addresses), lambda (Lambda functions), or alb (Application Load Balancers). The default is instance.
What are the target type restrictions by load balancer?
Network Load Balancers don’t support the lambda target type. Application Load Balancers don’t support the alb target type.
What properties are required for each target type?
For instance, ip, or alb targets, you must specify port, protocol, and vpcId. For lambda targets, these properties don’t apply and shouldn’t be specified.
Can I mix EC2 instances and IP addresses in the same target group?
No, you can’t register targets using both instance IDs and IP addresses in the same target group. Choose either instance or ip as your target type.
What IP addresses can I use with the ip target type?
You can use IPs from VPC subnets, RFC 1918 ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), and RFC 6598 range (100.64.0.0/10). Publicly routable IP addresses aren’t allowed.
Immutability & Lifecycle
What properties can't I change after creating a target group?
These properties are immutable and require replacement: name, namePrefix, targetType, port, protocol, protocolVersion, vpcId, ipAddressType, and targetControlPort.
Load Balancing Algorithms & Features
What load balancing algorithms are available?
Application Load Balancers support three algorithms: round_robin (default), least_outstanding_requests, and weighted_random. These only apply to ALB target groups.
When should I enable anomaly mitigation?
Target anomaly mitigation is only supported with the weighted_random load balancing algorithm. Set loadBalancingAlgorithmType to weighted_random before enabling it.
What's the default for cross-zone load balancing?
The default is use_load_balancer_configuration, which inherits the setting from the load balancer. You can override it with true or false.
Deregistration & Health Checks
What's the default deregistration delay?
The default deregistration delay is 300 seconds (5 minutes). You can configure it between 0 and 3600 seconds.
How do I enable slow start for targets?
Set slowStart to a value between 30 and 900 seconds for the warm-up period. The default is 0, which disables slow start.
How do I disable unhealthy connection termination on NLB?
Configure targetHealthStates with enableUnhealthyConnectionTermination set to false. This only applies to Network Load Balancers with TCP or TLS protocols.
Naming & Constraints
What are the naming constraints for target groups?
Names must be unique per region per account, have a maximum of 32 characters, contain only alphanumeric characters or hyphens, and can’t begin or end with a hyphen.
What's the difference between name and namePrefix?
Use name to specify the full name, or namePrefix (max 6 characters) to generate a unique name with that prefix. These properties conflict with each other.

Using a different cloud?

Explore networking guides for other cloud providers: