Getting this one wrong is not a configuration mistake you fix on a Friday afternoon. The three services sit at different layers, accept different protocols, and several of the choices you make at creation time cannot be changed afterwards. Here is what each is for, where the protocol list decides for you, and which settings deserve a second look before you commit.
What each one is actually for
An Application Load Balancer functions at the application layer, the seventh layer of the OSI model. It reads the request, and it can route on the URL path, the host header, other HTTP headers, the method, query parameters and the source IP. Listener protocols are HTTP and HTTPS, on ports 1 to 65535. That is the entire list. If your traffic is not HTTP, an ALB cannot carry it.
A Network Load Balancer functions at the fourth layer. It does not read your request, it forwards a flow. Listener protocols are TCP, TLS, UDP, TCP_UDP, QUIC and TCP_QUIC. Elastic Load Balancing creates a network interface in each Availability Zone you enable, and each load balancer node uses that interface to get a static IP address. On an internet-facing NLB you can associate one Elastic IP address per subnet, which is the reason most teams reach for it, a fixed address a customer can put in their firewall.
CloudFront is a content delivery network and it serves content over HTTP or HTTPS. Origins can be an S3 bucket, an Elastic Load Balancing load balancer, a media service channel or any HTTP server, in any combination of up to 25 origins on a single distribution. By default each file stays in an edge location for 24 hours before it expires.
Where the protocol list decides it for you
Most of the time you do not choose. The protocol chooses.
WebSockets is the case people get wrong most often. An ALB provides native WebSocket support by upgrading an existing HTTP/1.1 connection, and you can use it on both HTTP and HTTPS listeners. On an NLB, WebSockets is supported only on TCP, TLS, TCP_UDP and TCP_QUIC listeners. CloudFront enables WebSocket functionality automatically on any distribution, but only over HTTP/1.1, and the cache behaviour has to forward the Sec-WebSocket-Key and Sec-WebSocket-Version headers to your origin, either by naming them in an origin request policy or by using the AllViewer managed policy.
HTTP/2 on an ALB works on HTTPS listeners, carrying up to 128 requests in parallel on one connection, and the HTTP/2 server push feature is not available. On CloudFront, HTTP/2 requires viewers to support TLS 1.2 or later and SNI, and HTTP/3 requires TLS 1.3 and SNI.
Mutual TLS splits them cleanly. An ALB supports mutual TLS authentication, including with HTTP/2, in both passthrough and verify modes. Network Load Balancers do not support it on a TLS listener, and the documented workaround is a TCP listener instead so the load balancer passes the connection through and your target implements mTLS itself.
Anything that is not HTTP, so a game protocol over UDP, a database wire protocol, SMTP, or media over RTP, has only one possible answer of the three.
When the answer is two of them
CloudFront in front of an ALB is the common pair. The distribution terminates TLS at the edge, absorbs the cacheable traffic and forwards the rest to the ALB as its origin, so you keep path-based routing and gain edge caching and a place to attach a web ACL.
The less obvious pair is an ALB registered as a target of an NLB. You create a target group of type alb, register a single Application Load Balancer in it, and point a TCP listener at that target group. AWS gives two reasons for doing this. You get the layer 7 request-based routing of the ALB combined with features the NLB supports, such as PrivateLink endpoint services and static IP addresses. It also serves applications that need one endpoint for multiple protocols, for example media services using HTTP for signalling and RTP to stream content.
Read the constraints before committing to it. Only one Application Load Balancer per target group, both load balancers in the same VPC within the same account, and an ALB can be a target of at most two Network Load Balancers using a separate target group for each. Only a TCP listener can forward to it. When the target group type is alb you cannot modify the target group attributes at all, and they always use their default values.
aws elbv2 create-target-group \
--name alb-behind-nlb \
--protocol TCP \
--port 443 \
--target-type alb \
--vpc-id vpc-0123456789abcdef0
The decisions that are hard to reverse
A few settings are chosen once and only changed by rebuilding.
A target group's target type cannot be changed after the target group is created. If you build instance target groups and later want to register IP addresses, or want to put the ALB behind an NLB, you create new target groups and move the listener across.
Security groups on a Network Load Balancer are the sharp one. If you do not associate any security groups with the NLB when you create it, you cannot associate them later. The inverse is also worth knowing at design time, because to use a QUIC or TCP_QUIC listener the Network Load Balancer must have no security groups.
A certificate used to require HTTPS between viewers and CloudFront has to be requested or imported in the US East (N. Virginia) Region, us-east-1, whatever Region everything else lives in. A certificate for the connection between CloudFront and an Elastic Load Balancing origin can be requested in any Region.
Removing a subnet from a live NLB is disruptive rather than irreversible, but it catches people. When a subnet is removed its network interface is deleted, which terminates all active connections in that Availability Zone, and every target in that zone is marked unused, including connections arriving from other zones through cross-zone load balancing.
The most expensive decision is not an AWS setting at all. It is the hostname you hand out to clients. Put your own domain in front of whichever of these you pick, with a short TTL, and you keep the ability to change your mind later.
Deciding in one pass
If the traffic is HTTP and you need to route on its content, that is an ALB. If it is not HTTP, or a third party needs to allowlist a fixed IP address, or you are publishing the service through PrivateLink, that is an NLB. If the traffic is global, largely cacheable, or you want TLS terminated closer to your users, put CloudFront in front of whichever one you already chose.
Where the answer is genuinely unclear, it is worth settling in architecture and planning rather than in production, and running the result afterwards is ordinary AWS cloud management.
Talk to the engineer who will own your stack.
No account managers, no offshore handoff. Senior DevOps, direct. Tell us what you are dealing with and you get a straight answer.
Related Articles
AWS Cost Optimization: 10 Things You're Probably Overpaying For
Ten common areas where AWS customers overspend, with practical strategies for right-sizing, reserved capacity, storage lifecycle management, and more.
CloudCloudflare Tunnel vs AWS ALB: When to Use Which
An architecture comparison of Cloudflare Tunnel and AWS Application Load Balancer, covering cost, DDoS protection, SSL termination, latency, and setup complexity.
CloudAWS Cost Optimization Strategies for Growing SaaS
Reduce your AWS bill by 30-50% with Reserved Instances, Spot Fleets, right-sizing, and architectural patterns designed for cost-efficient SaaS growth.