All posts

Amazon ECS Gateway: managed ingress for every domain except yours

What Amazon ECS Gateway is, what it creates for each service, why your services end up on on.aws hostnames instead of your own domain, and the other limits to know.

AWS has just released Amazon ECS Gateway (October 2026), a new way to get traffic into ECS services. It takes over a job most teams have been doing by hand.

Most teams put their ECS services behind one shared Application Load Balancer. Each new service then needs the same handful of things: a target group, a listener rule, a certificate, a DNS record and a security group rule that lets the load balancer reach the tasks. Usually that lives in Terraform or CloudFormation next to the service, and someone copies it for every new service.

ECS Gateway moves that work into ECS. A platform team sets up a gateway once, and a service connects to it with one setting. ECS creates everything in between, adds load balancers when they fill up and cleans up when the service goes away.

The four pieces

PieceWhat it is
GatewayAn account-level resource that holds the ingress settings: public or internal, subnets, WAF, access logs. It manages the load balancers for you.
Cluster associationLinks a gateway to a cluster, so the cluster’s services can use it. A gateway can serve many clusters, and a cluster can have up to 5 gateways.
Gateway connectorA setting on the service that says “connect me”, naming the container port that gets traffic.
Gateway routeOne connection between a service and a gateway. ECS creates it when the service connects and deletes it when it disconnects.

The split is deliberate. The platform team owns the gateway and decides how traffic gets in. Application teams only add a connector to their service.

Setting one up

The gateway needs an IAM role that ECS assumes to manage load balancers, certificates and security groups in your account. AWS provides a managed policy for it:

aws iam create-role \
  --role-name ecsInfrastructureRoleForGateways \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": { "Service": "ecs.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }]
  }'

aws iam attach-role-policy \
  --role-name ecsInfrastructureRoleForGateways \
  --policy-arn arn:aws:iam::aws:policy/AmazonECSInfrastructureRoleForGatewayHostSharedALB

Then create the gateway and associate it with a cluster:

aws ecs create-gateway \
  --name my-gateway \
  --infrastructure-role-arn arn:aws:iam::123456789012:role/ecsInfrastructureRoleForGateways \
  --configuration '{
    "hostSharedAlb": {
      "scheme": "internet-facing",
      "networkConfiguration": {
        "subnets": ["subnet-abc123", "subnet-def456"]
      }
    }
  }'

aws ecs associate-gateway-with-cluster --gateway my-gateway --cluster my-cluster

Nothing is provisioned yet, and nothing costs money yet. The first load balancer appears when the first service connects:

aws ecs update-service \
  --cluster my-cluster \
  --service web \
  --gateway-connectors '[{
    "portName": "web",
    "healthCheck": { "healthCheckPath": "/health" }
  }]'

portName must match a named port mapping in the task definition. Without a health check path, the load balancer checks / every 30 seconds.

What ECS creates

Each gateway shares a few resources across its services, and adds a few more for every service that connects:

  • Shared: the Application Load Balancer, its HTTPS listener on port 443 and the load balancer’s security group.
  • Per service: an ACM certificate, a DNS hostname, a listener rule that matches that hostname, a target group and a security group that lets only the load balancer reach the tasks.

Up to 25 services share one load balancer. The 26th gets a new one, up to the gateway’s maxAlbCount (10 by default). When every load balancer is full and the limit is reached, the deployment that tries to connect fails.

Try it below. Connect a few services, open one to see its resources and send a request through the chain. Then connect enough to fill a load balancer, and lower maxAlbCount to see the failure.

What a gateway creates

my-gateway · internet-facing · HTTPS :443 0 services · 0 of 2 load balancers

Nothing is provisioned yet. ECS creates the first load balancer when the first service connects.

Simplified: real hostnames are random, provisioning takes minutes, and the default maxAlbCount is 10.

Two things the diagram shows that are easy to miss. Deleting a service removes its route, and a load balancer goes away once its last service is gone. But ECS never moves services between load balancers, so after a lot of churn you can end up with several partly filled ones.

Finding the URL

Each service gets a hostname of the form https://<random-id>.ecs.<region>.on.aws. You find it on the service revision once the deployment has started:

aws ecs describe-service-revisions \
  --service-revision-arns <revision-arn> \
  --query 'serviceRevisions[0].ecsManagedResources.gatewayConnections'

Each connection has a serviceUrl, an accessType and a status. Send traffic only once the status is ACTIVE.

The id is random, not derived from the service name. If you delete and recreate a service, it gets a new URL.

No custom domains

This is the big catch. We wrote this post right after ECS Gateway launched, and at that point a gateway had no setting for your own domain or certificate. The on.aws hostname was the only address a service got. AWS may well have added custom domains since then, so check the ECS Gateway documentation before you rely on what follows.

A CNAME from api.example.com to the generated hostname doesn’t help. The certificate only covers the on.aws name, so clients reject the TLS connection, and the listener rule only matches the generated hostname anyway. Adding your own certificate and rule to the managed load balancer is no better: ECS doesn’t repair resources you change, and later deployments can fail.

The way around it is a CDN in front. Put your domain and certificate on a CloudFront distribution and use the generated hostname as its origin. CloudFront then calls the gateway with the generated hostname, so both the certificate and the listener rule match. The cost is one more service to pay for and configure, and a recreated service means updating the origin.

For internal services that only talk to each other, this matters less. For anything customers see, it’s the first thing to check before you move.

Which gateway a service uses

A service doesn’t name a gateway. It connects to every gateway associated with its cluster that passes three checks:

  1. It’s in the same VPC as the service.
  2. It covers every Availability Zone the service’s subnets are in.
  3. Its access type matches the connector’s filter, if there is one. An internet-facing gateway is PUBLIC, an internal one is PRIVATE.

Point 3 matters more than it looks. A connector without a filter accepts both, so if the platform team later adds a public gateway to the cluster, the next deployment makes your internal service reachable from the internet. Services that must never be public should say so:

aws ecs update-service \
  --cluster my-cluster \
  --service web \
  --gateway-connectors '[{
    "portName": "web",
    "filters": { "accessTypes": ["PRIVATE"] }
  }]'

If no gateway passes, the deployment fails. If the gateway misses one of the service’s zones, tasks in that zone never pass their health check and ECS keeps replacing them.

Limits worth knowing

  • HTTPS on port 443 only, and routing is by hostname. The launch documentation doesn’t mention path-based rules.
  • awsvpc only. EC2, Fargate and Managed Instances all work, but bridge and host tasks can’t connect.
  • Same account, same Region. A gateway can’t serve clusters in other accounts.
  • Not for Express Mode services, which bring their own load balancer setup.
  • Some settings are fixed at creation: name, scheme, subnets, IP address type, protocol and port. WAF, access logs, the security group and maxAlbCount can change later.
  • UpdateGateway replaces the whole configuration. A setting you leave out is cleared, not kept, so always send the full configuration.
  • Don’t edit the managed resources by hand. ECS doesn’t repair them, and later deployments or cleanup can fail.
  • Changing the health check replaces the target group. With the ROLLING deployment strategy that’s rejected, so set the health check when you first connect, or use blue/green or canary. Health check timing works the same as with any target group, which the health checks post covers.

What it costs

The gateway itself is free. You pay for what it creates: load balancer hours and capacity units, CloudWatch metrics and logs, and data transfer, the same as with a load balancer you manage yourself. With 25 services per load balancer, most setups need only one. The difference to your own setup is mostly that the gateway decides when another load balancer is needed, and doesn’t merge partly filled ones afterwards.

Summary

ECS Gateway doesn’t do anything a shared load balancer couldn’t do before. It takes the per-service setup off your hands: certificate, hostname, rule, target group and security group appear when a service connects and disappear when it’s deleted. The platform team keeps control of how traffic gets in, and application teams add one setting to their service.

Check the limits before you move to it: no custom domains, hostname routing only, and settings you can’t change later. For a single service with special routing needs, a load balancer you manage yourself is still the more flexible choice.