Configure AWS Load Balancer Target Groups

The aws:lb/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 are attached to load balancer listeners. The examples are intentionally small. Combine them with your own load balancers, health checks, and target registration.

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”, allowing you to register EC2 instances by ID. The port and protocol define how the load balancer forwards traffic. The vpcId places the target group in your VPC’s network space.

Route traffic to specific IP addresses

Container workloads and services outside EC2 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 registration of container task IPs, on-premises service IPs, or any address in VPC subnets or RFC 1918 ranges. You cannot register publicly routable IPs.

Invoke Lambda functions from load balancer requests

Application Load Balancers can invoke Lambda functions directly for serverless request handling.

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 function with HTTP request details and expects an HTTP response structure.

Preserve connections to unhealthy targets

Network Load Balancers normally terminate connections when targets fail health checks, but some workloads need to complete in-flight requests.

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 allows existing connections to finish even after the target becomes unhealthy. This only applies to Network Load Balancers with TCP or TLS protocols.

Control DNS failover and unhealthy routing thresholds

Route 53 health checks and load balancer routing can require minimum healthy target counts before triggering 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 thresholds for DNS failover and unhealthy state routing. The dnsFailover section controls when Route 53 marks the target group unhealthy. The unhealthyStateRouting section determines when the load balancer routes traffic to unhealthy targets as a last resort. Both accept either a count or percentage threshold.

Beyond these examples

These snippets focus on specific target group features: target types (instance, IP, Lambda, ALB) and connection termination and health routing controls. They’re intentionally minimal rather than full load balancing configurations.

The examples may reference pre-existing infrastructure such as VPCs (created inline but typically pre-existing) and load balancer resources (not shown in target group examples). 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, weighted random)

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 are the target type options and their load balancer compatibility?
Target groups support four types: instance (default), ip, lambda, and alb. Network Load Balancers don’t support lambda targets, and Application Load Balancers don’t support alb targets.
Can I register both instance IDs and IP addresses in the same target group?
No, you can’t mix instance IDs and IP addresses when registering targets. Choose either instance or ip as your target type and register targets consistently.
Can I use public IP addresses with the ip target type?
No, only private IP ranges are allowed: 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 supported.
Immutable Properties
What properties can't be changed after creating a target group?
These properties are immutable and force replacement: name, namePrefix, targetType, port, protocol, vpcId, ipAddressType, protocolVersion, and targetControlPort.
Configuration Requirements
Which properties are required for Lambda targets vs other target types?
Lambda targets only need targetType set to lambda. Other target types (instance, ip, alb) require port, protocol, and vpcId.
What are the naming constraints for target groups?
Names can have a maximum of 32 characters, must contain only alphanumeric characters or hyphens, and can’t begin or end with a hyphen. Use namePrefix (max 6 characters) to auto-generate unique names.
Load Balancing & Performance
What load balancing algorithms are available?
Application Load Balancers support three algorithms: round_robin (default), least_outstanding_requests, and weighted_random. The algorithm determines how the load balancer selects targets when routing requests.
How do I enable target anomaly mitigation?
Set loadBalancingAnomalyMitigation to "on". This feature only works with the weighted_random load balancing algorithm.
What are the deregistration delay settings?
Deregistration delay controls how long (0-3600 seconds) the load balancer waits before changing a deregistering target from draining to unused. The default is 300 seconds.
How does slow start work?
Slow start gives targets time to warm up (30-900 seconds) before receiving a full share of requests. Set slowStart to the desired warm-up period, or 0 to disable (default is 0).

Using a different cloud?

Explore networking guides for other cloud providers: