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 ALBs. This guide focuses on three capabilities: target type selection, connection termination behavior, and health-based routing thresholds.
Target groups belong to a VPC (except Lambda targets) and attach to Application, Network, or Gateway Load Balancers. The examples are intentionally small. Combine them with your own VPC, load balancer, and target registration resources.
Route traffic to EC2 instances by ID
Most deployments route HTTP or HTTPS traffic to EC2 instances within a VPC.
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 targets by EC2 instance ID. The port and protocol define where the load balancer sends traffic and how it performs health checks. The vpcId places the target group in your VPC. Targets are registered separately using aws.lb.TargetGroupAttachment.
Route traffic to specific IP addresses
Container workloads and on-premises services often expose endpoints via IP addresses 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” allows you to register targets by IP address. This supports container tasks (ECS, EKS), on-premises servers via Direct Connect or VPN, and any endpoint with a VPC-internal or RFC 1918 address. You cannot use publicly routable IPs.
Invoke Lambda functions from a load balancer
Application Load Balancers can invoke Lambda functions directly for serverless HTTP endpoints.
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 doesn’t require port, protocol, or vpcId. The load balancer invokes your Lambda function with HTTP request details and expects an HTTP response. This eliminates the need for API Gateway in simple serverless architectures.
Preserve connections to unhealthy targets
Network Load Balancers normally terminate connections when targets fail health checks. Some protocols need graceful handling.
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 even when targets become unhealthy. This is useful for long-lived TCP connections that should drain naturally rather than being forcibly closed.
Configure DNS failover and routing thresholds
Route 53 health checks and unhealthy state routing use target group health metrics to control DNS failover.
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: 80,
protocol: "TCP",
vpcId: main.id,
targetGroupHealth: {
dnsFailover: {
minimumHealthyTargetsCount: "1",
minimumHealthyTargetsPercentage: "off",
},
unhealthyStateRouting: {
minimumHealthyTargetsCount: 1,
minimumHealthyTargetsPercentage: "off",
},
},
});
import pulumi
import pulumi_aws as aws
tcp_example = aws.lb.TargetGroup("tcp-example",
name="tf-example-lb-nlb-tg",
port=80,
protocol="TCP",
vpc_id=main["id"],
target_group_health={
"dns_failover": {
"minimum_healthy_targets_count": "1",
"minimum_healthy_targets_percentage": "off",
},
"unhealthy_state_routing": {
"minimum_healthy_targets_count": 1,
"minimum_healthy_targets_percentage": "off",
},
})
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(80),
Protocol: pulumi.String("TCP"),
VpcId: pulumi.Any(main.Id),
TargetGroupHealth: &lb.TargetGroupTargetGroupHealthArgs{
DnsFailover: &lb.TargetGroupTargetGroupHealthDnsFailoverArgs{
MinimumHealthyTargetsCount: pulumi.String("1"),
MinimumHealthyTargetsPercentage: pulumi.String("off"),
},
UnhealthyStateRouting: &lb.TargetGroupTargetGroupHealthUnhealthyStateRoutingArgs{
MinimumHealthyTargetsCount: pulumi.Int(1),
MinimumHealthyTargetsPercentage: pulumi.String("off"),
},
},
})
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 = 80,
Protocol = "TCP",
VpcId = main.Id,
TargetGroupHealth = new Aws.LB.Inputs.TargetGroupTargetGroupHealthArgs
{
DnsFailover = new Aws.LB.Inputs.TargetGroupTargetGroupHealthDnsFailoverArgs
{
MinimumHealthyTargetsCount = "1",
MinimumHealthyTargetsPercentage = "off",
},
UnhealthyStateRouting = new Aws.LB.Inputs.TargetGroupTargetGroupHealthUnhealthyStateRoutingArgs
{
MinimumHealthyTargetsCount = 1,
MinimumHealthyTargetsPercentage = "off",
},
},
});
});
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.TargetGroupTargetGroupHealthArgs;
import com.pulumi.aws.lb.inputs.TargetGroupTargetGroupHealthDnsFailoverArgs;
import com.pulumi.aws.lb.inputs.TargetGroupTargetGroupHealthUnhealthyStateRoutingArgs;
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(80)
.protocol("TCP")
.vpcId(main.id())
.targetGroupHealth(TargetGroupTargetGroupHealthArgs.builder()
.dnsFailover(TargetGroupTargetGroupHealthDnsFailoverArgs.builder()
.minimumHealthyTargetsCount("1")
.minimumHealthyTargetsPercentage("off")
.build())
.unhealthyStateRouting(TargetGroupTargetGroupHealthUnhealthyStateRoutingArgs.builder()
.minimumHealthyTargetsCount(1)
.minimumHealthyTargetsPercentage("off")
.build())
.build())
.build());
}
}
resources:
tcp-example:
type: aws:lb:TargetGroup
properties:
name: tf-example-lb-nlb-tg
port: 80
protocol: TCP
vpcId: ${main.id}
targetGroupHealth:
dnsFailover:
minimumHealthyTargetsCount: '1'
minimumHealthyTargetsPercentage: off
unhealthyStateRouting:
minimumHealthyTargetsCount: '1'
minimumHealthyTargetsPercentage: off
The targetGroupHealth block sets minimum healthy target requirements for DNS failover and unhealthy state routing. The dnsFailover section controls when Route 53 marks the target group as unhealthy. The unhealthyStateRouting section determines when the load balancer routes traffic to unhealthy targets as a last resort. You can specify thresholds as absolute counts or percentages.
Beyond these examples
These snippets focus on specific target group features: target types (instance, IP, Lambda, ALB) and connection termination and health routing. They’re intentionally minimal rather than full load balancing configurations.
The examples may reference pre-existing infrastructure such as VPCs (examples create inline but typically reference existing), load balancers (target groups attach to ALB/NLB/GWLB), and targets (instances, IPs, Lambda functions registered separately). They focus on configuring the target group rather than provisioning the complete load balancing stack.
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 (round robin, least outstanding requests)
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 FREEFrequently Asked Questions
Target Types & Compatibility
instance (default), ip, lambda, or alb. The target type determines which other properties are required and affects load balancer compatibility.lambda target type. Use an Application Load Balancer for Lambda targets instead.alb target type. Use a Network Load Balancer to route traffic to other ALBs.Requirements vary by target type:
- instance/ip/alb: Require
port,protocol, andvpcId - lambda: Only requires
targetType(no port, protocol, or vpcId needed)
IP Addressing & Target Registration
targetType of ip, you can only specify private IP addresses from VPC subnets, RFC 1918 ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), or RFC 6598 range (100.64.0.0/10).ipAddressType (with values ipv4 or ipv6) only when targetType is set to ip. This property is immutable after creation.Immutability & Naming
name, namePrefix, targetType, port, protocol, vpcId, protocolVersion, ipAddressType, and targetControlPort.namePrefix (max 6 characters) for auto-generated names, but it conflicts with name.Load Balancing Algorithms
round_robin (default), least_outstanding_requests, and weighted_random. This setting only applies to ALB target groups.loadBalancingAnomalyMitigation to "on", but this only works when loadBalancingAlgorithmType is set to weighted_random. The default is "off".Connection & Deregistration
deregistrationDelay ranges from 0 to 3600 seconds, with a default of 300 seconds. This controls how long the load balancer waits before changing a deregistering target from draining to unused.slowStart warm-up period ranges from 30 to 900 seconds, or 0 to disable. The default is 0 seconds (disabled).loadBalancingCrossZoneEnabled property controls whether traffic is distributed across availability zones. Values are "true", "false", or "use_load_balancer_configuration" (default).Using a different cloud?
Explore networking guides for other cloud providers: