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 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 FREE

Frequently Asked Questions

Target Types & Compatibility
What target types are available?
You can choose from four target types: instance (default), ip, lambda, or alb. The target type determines which other properties are required and affects load balancer compatibility.
Can I use Lambda targets with Network Load Balancers?
No, Network Load Balancers don’t support the lambda target type. Use an Application Load Balancer for Lambda targets instead.
Can I use ALB targets with Application Load Balancers?
No, Application Load Balancers don’t support the alb target type. Use a Network Load Balancer to route traffic to other ALBs.
What properties are required for each target type?

Requirements vary by target type:

  • instance/ip/alb: Require port, protocol, and vpcId
  • lambda: Only requires targetType (no port, protocol, or vpcId needed)
IP Addressing & Target Registration
Can I use public IP addresses as targets?
No, when using 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).
Can I mix instance IDs 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 one target type and stick with it.
When should I use ipAddressType?
Use ipAddressType (with values ipv4 or ipv6) only when targetType is set to ip. This property is immutable after creation.
Immutability & Naming
What properties can't I change after creating a target group?
These properties are immutable and require replacement: name, namePrefix, targetType, port, protocol, vpcId, protocolVersion, ipAddressType, and targetControlPort.
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. Use namePrefix (max 6 characters) for auto-generated names, but it conflicts with name.
Load Balancing Algorithms
What load balancing algorithms are available?
Application Load Balancers support three algorithms: round_robin (default), least_outstanding_requests, and weighted_random. This setting only applies to ALB target groups.
How do I enable anomaly mitigation?
Set loadBalancingAnomalyMitigation to "on", but this only works when loadBalancingAlgorithmType is set to weighted_random. The default is "off".
Connection & Deregistration
What are the deregistration delay limits?
The 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.
What are the slow start limits?
The slowStart warm-up period ranges from 30 to 900 seconds, or 0 to disable. The default is 0 seconds (disabled).
What does cross-zone load balancing do?
The 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: