An architecture comparison of Cloudflare Tunnel and AWS Application Load Balancer, covering cost, DDoS protection, SSL termination, latency, and setup complexity.
Introduction
When exposing a web application to the internet, two approaches dominate in 2026: Cloudflare Tunnel and AWS Application Load Balancer. Both route traffic to backend services, terminate TLS, and offer some protection. They differ in architecture, in cost model, and in what happens when things go wrong.
This article breaks down when to use each, what they actually cost with current published pricing, and when to combine them.
Architecture Overview
The fundamental difference is the direction the connection is established, and that single detail drives most of what follows.
Cloudflare Tunnel
Cloudflare Tunnel runs a lightweight daemon (cloudflared) on your server. The daemon dials outbound to Cloudflare's edge and holds the connection open. No inbound port is ever opened on the origin.
outbound only, no inbound ports
|
User ──> Cloudflare PoP ──────┴────> cloudflared ──> Application
(TLS terminated) tunnel (localhost)
DDoS + WAF here origin IP never public
AWS ALB
An ALB is a managed load balancer inside your VPC. It listens on public 80/443, terminates TLS, and forwards to a target group.
User ──> ALB (public subnet) ──> Target Group ──> App (private subnet)
TLS terminated health checks
public IP exposed EC2 / ECS / Lambda / IP
The ALB has a public IP by design. The Tunnel origin does not have one at all. That is the security story in one line, and it is also the cost story, because the ALB path bills the bandwidth that leaves AWS while the Tunnel path does not.
The Cost Question, With Real Numbers
Published pricing as of July 2026, US East (N. Virginia):
| Component | Cloudflare Tunnel | AWS ALB |
|---|---|---|
| Base cost | Free on every plan, including Free | $0.0225 per hour = $16.43/month |
| Capacity charge | None | $0.008 per LCU-hour (see below) |
| Egress to internet | Free, unmetered | First 100 GB free, then $0.09/GB to 10 TB |
| TLS certificates | Free (Origin CA, 15 years) | Free via ACM |
| DDoS L3/L4 | Included, unmetered | Shield Standard, free |
| DDoS L7 | Included, unmetered | Shield Advanced, $3,000/month, 1-year commitment |
| WAF | Included; Pro plan $20/mo annual, $25/mo monthly | $5 per Web ACL + $1 per rule + $0.60 per million requests |
Two of those lines deserve emphasis because they are where the bills actually come from.
Understanding LCUs, the ALB charge nobody budgets for
The $16.43 is the part people quote. The LCU is the part that surprises them. An ALB bills the highest of four dimensions, each measured per hour:
| LCU dimension | One LCU equals |
|---|---|
| New connections | 25 per second |
| Active connections | 3,000 per minute (1,500 with mutual TLS) |
| Processed bytes | 1 GB/hour for EC2, containers, IP targets (0.4 GB/hour for Lambda) |
| Rule evaluations | 1,000 per second (first 10 rules free) |
You are not billed for the sum. You are billed for whichever dimension is highest in that hour. For ordinary web traffic, processed bytes is almost always the one that wins, which means your LCU bill tracks your bandwidth twice: once as LCUs, and again as egress.
A worked example
A modest production app: 2,000 GB of egress a month, 2 million requests, five WAF rules.
AWS ALB path
ALB base 0.0225/hr x 730 hr = $ 16.43
LCU (bytes) 2000 GB / 730 hr = 2.74 GB/hr
2.74 LCU x $0.008 x 730 hr = $ 16.00
Egress (2000 - 100 free) GB x $0.09 = $171.00
WAF $5 ACL + (5 x $1) + 2M x $0.60/M = $ 11.20
────────
TOTAL $214.63
Cloudflare Tunnel path
Tunnel = $ 0.00
Egress unmetered = $ 0.00
WAF Pro plan (optional, annual) = $ 20.00
────────
TOTAL $ 20.00
Look at where the AWS money goes: $171 of $214, about 80 percent, is bandwidth. The load balancer itself is nearly a rounding error next to the egress. This is the single most important number in the comparison, and it is why the gap widens as you grow. At 10 TB a month the ALB path crosses roughly $900 in egress alone while the Tunnel path is still billing zero for it.
The honest caveat: these numbers assume traffic leaves AWS. If your users are inside AWS, or you serve most bytes from CloudFront with its own pricing, the arithmetic changes. Run your own numbers against the AWS pricing calculator rather than trusting any blog's example, this one included.
DDoS Protection
Cloudflare's network is built around DDoS mitigation. Every request passes the edge before reaching your origin, and with Tunnel the origin IP is never published at all. There is nothing to attack directly. L3, L4, and L7 mitigation is unmetered on every plan, including Free.
AWS is a different shape. Shield Standard is free and automatic, but it covers L3 and L4 only. Application-layer DDoS protection means either writing rate-limiting rules in AWS WAF yourself, or subscribing to Shield Advanced.
Shield Advanced is where the number gets misquoted. It is $3,000 per month with a one-year subscription commitment, so the real entry price is $36,000, not $3,000. That is a procurement decision, not a checkbox. For most teams under enterprise scale, the practical answer on AWS is WAF rate limiting, which lands you back at the per-rule and per-million-request charges above.
TLS and Certificates
Both terminate TLS competently, but the certificate models differ in a way worth understanding.
Cloudflare Tunnel terminates TLS at the edge. The hop from Cloudflare to your origin is encrypted with a Cloudflare Origin CA certificate: free, valid up to 5,475 days (roughly 15 years), available on every plan. The catch, and it matters: an Origin CA certificate is only trusted by Cloudflare, not by browsers. It works because traffic is proxied through Cloudflare. Point a client at that origin directly and it will reject the certificate. That is a feature, not a bug, but it does mean the origin is not independently servable.
Cloudflare also does not send expiry notifications for Origin CA certificates. A fifteen-year certificate is a fifteen-year reminder you have to set yourself.
AWS ALB terminates with ACM certificates, free and auto-renewing, trusted publicly. Backend hops can use self-signed certificates because the ALB does not validate them.
Setup Complexity
Cloudflare Tunnel
# Install cloudflared
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 \
-o /usr/local/bin/cloudflared
chmod +x /usr/local/bin/cloudflared
# Authenticate and create the tunnel
cloudflared tunnel login
cloudflared tunnel create myapp
# Configure ingress rules
cat > ~/.cloudflared/config.yml << 'EOF'
tunnel: <TUNNEL_ID>
credentials-file: /root/.cloudflared/<TUNNEL_ID>.json
ingress:
- hostname: myapp.example.com
service: http://localhost:3000
- service: http_status:404
EOF
# Route DNS and run as a service
cloudflared tunnel route dns myapp myapp.example.com
cloudflared service install
systemctl enable --now cloudflared
That is the whole thing. Roughly six commands, no firewall changes, no public IP.
AWS ALB
The equivalent in Terraform, trimmed to essentials:
resource "aws_lb" "app" {
name = "app-alb"
load_balancer_type = "application"
subnets = var.public_subnet_ids # needs >= 2 AZs
security_groups = [aws_security_group.alb.id]
}
resource "aws_lb_target_group" "app" {
name = "app-tg"
port = 3000
protocol = "HTTP"
vpc_id = var.vpc_id
health_check {
path = "/healthz"
healthy_threshold = 2
unhealthy_threshold = 3
interval = 15
}
}
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.app.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = aws_acm_certificate.app.arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
Plus the ACM certificate with DNS validation, the security group, a Route 53 record, and subnets in two availability zones. It is not hard, but it is an order of magnitude more moving parts than six commands.
The Operational Differences That Actually Bite
Cost and setup get the attention. These are the things you notice at 3am.
Health checks. The ALB actively probes your targets and pulls unhealthy ones out of rotation. That is real, valuable behaviour you do not get from a plain Tunnel, where the daemon is either connected or it is not. If your app can be unhealthy while its process is alive, the ALB knows and Tunnel does not.
Failover across instances. ALB spreads across targets in multiple availability zones by design. Tunnel can run multiple cloudflared replicas against the same tunnel for redundancy, but you are assembling that yourself rather than getting it as a managed property.
The daemon is now a dependency. Tunnel moves the failure mode from "port is closed" to "the daemon died". It is one more process to monitor, restart, and upgrade. Cloudflare cannot reach an origin whose cloudflared is wedged, and there is no public IP to fall back to.
Debugging. ALB access logs land in S3 in a documented format. Tunnel diagnostics live in Cloudflare's dashboard and cloudflared logs. Neither is worse, but they are different tools and different muscle memory.
Latency. Be sceptical of universal claims here, including ours. Cloudflare terminates at a PoP that is usually closer to the user than your region is, which helps first-byte time, but the tunnel hop back to your origin is a real hop. An ALB sitting in the same region as its targets has a shorter path to the backend but a longer one to a distant user. Which wins depends on where your users are relative to your origin. Measure yours instead of assuming.
When to Use Cloudflare Tunnel
- The origin should have no inbound ports open at all. This is the strongest argument and it is a big one.
- Bandwidth is meaningful and you would rather not pay per GB for it.
- The infrastructure is bare metal, a VPS, a home lab, or a K3s cluster with no cloud load balancer.
- You want DDoS and WAF included rather than assembled and billed per rule.
When to Use AWS ALB
- The backend is AWS-native (ECS, EKS, Lambda) and you want target-group health checks and AZ failover as managed properties.
- You need tight integration with AWS WAF, Cognito authentication, or ALB-native routing rules like weighted target groups.
- Traffic is largely internal to AWS, which removes the egress argument entirely.
- Your team already operates a mature AWS estate and a second vendor is a real organisational cost.
Combining Both, Properly
In most of our production setups we run both. Cloudflare is the edge and shield, the ALB does routing inside AWS:
User ──> Cloudflare Edge ──> ALB (SG restricted to CF ranges) ──> ECS Tasks
WAF, DDoS, cache health checks, routing
The part that is usually skipped: the ALB security group must actually be locked to Cloudflare's ranges, otherwise you have simply added a CDN in front of a still-public load balancer and an attacker can hit the ALB directly and bypass every protection you just paid for.
# Fetch Cloudflare's published ranges rather than hardcoding them.
data "cloudflare_ip_ranges" "cf" {}
resource "aws_security_group" "alb" {
name = "alb-cloudflare-only"
vpc_id = var.vpc_id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = data.cloudflare_ip_ranges.cf.ipv4_cidr_blocks
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
ipv6_cidr_blocks = data.cloudflare_ip_ranges.cf.ipv6_cidr_blocks
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
Cloudflare publishes those ranges at cloudflare.com/ips and they change. Pull them from the provider or automate the refresh; a hardcoded list from 2023 is a future outage. Verify the lock actually holds by curling the ALB's DNS name directly from outside AWS. If it answers, you are not protected.
The Decision, Compressed
| If this is true | Lean |
|---|---|
| Origin must have zero open inbound ports | Tunnel |
| Egress is more than a few hundred GB a month | Tunnel |
| Running on bare metal, VPS, or K3s | Tunnel |
| Backend is ECS/EKS/Lambda inside AWS | ALB |
| You need managed health checks and AZ failover | ALB |
| Traffic stays inside AWS | ALB |
| Public internet app, AWS backend, cost and security both matter | Both |
Cloudflare Tunnel wins on simplicity, cost, and origin exposure when you are not deep in AWS. ALB wins when the backend is AWS-native and you want health checking and failover handed to you. For a public app on AWS, running both is not a compromise, it is the configuration we reach for most often, provided the security group is genuinely locked down.
If you want a second opinion on which shape fits your traffic, or a review of whether your current setup is actually restricted the way you think it is, that is what our cloud management and infrastructure management work covers.
Sources
Frequently asked questions
How much does an AWS Application Load Balancer cost per month?
An AWS ALB in US East (N. Virginia) costs $0.0225 per hour, which is $16.43 a month, based on published pricing as of July 2026. Capacity is billed on top at $0.008 per LCU-hour, and internet egress costs $0.09 per GB after the first 100 GB, up to 10 TB. The $16.43 is the figure people quote, while the LCU and egress lines are where the real bill comes from.
When should I use an AWS ALB instead of Cloudflare Tunnel?
Choose an AWS ALB when the backend is AWS-native, such as ECS, EKS or Lambda, and target-group health checks with availability-zone failover are wanted as managed features. ALB also makes sense when traffic stays mostly inside AWS, which removes the egress argument, or when the stack depends on AWS WAF, Cognito authentication or weighted target groups. Cloudflare Tunnel suits bare metal, a VPS or a K3s cluster where the origin should have no open inbound ports.
How does Cloudflare Tunnel cost compare with an AWS ALB at 2,000 GB of egress a month?
For an app with 2,000 GB of egress a month, 2 million requests and five WAF rules, the ALB path costs about $214.63 a month and the Cloudflare Tunnel path $20.00. The tunnel is free on every plan with unmetered egress, so the $20 is an optional Pro plan for WAF. On the AWS side, $171 of the total, about 80 percent, is bandwidth. The figures assume traffic leaves AWS and use US East pricing published as of July 2026.
What is an LCU on an AWS Application Load Balancer?
An LCU is the capacity unit an AWS ALB bills at $0.008 per LCU-hour, measured on whichever of four dimensions is highest in that hour. One LCU equals 25 new connections per second, 3,000 active connections per minute (1,500 with mutual TLS), 1 GB per hour of processed bytes for EC2, container and IP targets, or 1,000 rule evaluations per second. Processed bytes usually wins for web traffic, so the LCU charge tracks bandwidth a second time on top of egress.
Does AWS Shield Standard protect against layer 7 DDoS attacks?
No. AWS Shield Standard is free and automatic but covers L3 and L4 only. Application-layer protection on AWS means writing rate-limiting rules in AWS WAF or subscribing to Shield Advanced at $3,000 a month with a one-year commitment, a real entry price of $36,000. Cloudflare includes unmetered L3, L4 and L7 mitigation on every plan, and with Tunnel the origin IP is never published.
Do browsers trust Cloudflare Origin CA certificates?
No. A Cloudflare Origin CA certificate is trusted only by Cloudflare, so a client connecting straight to the origin rejects it, and the origin cannot be served independently. The certificate is free on every plan and valid for up to 5,475 days, about 15 years. Cloudflare sends no expiry notifications for Origin CA certificates, so the renewal reminder has to be set by hand.
What are the downsides of Cloudflare Tunnel compared with an AWS ALB?
Cloudflare Tunnel has no active health checks, since the cloudflared daemon is either connected or it is not. An app that is unhealthy while its process is alive goes unnoticed, where an ALB would pull that target out of rotation. The daemon is also one more process to monitor, restart and upgrade, and if it wedges there is no public IP to fall back to.
How do I lock an AWS ALB to Cloudflare IP ranges?
Restrict the ALB security group so port 443 accepts traffic only from Cloudflare's published IPv4 and IPv6 ranges, otherwise an attacker can reach the ALB directly and bypass every Cloudflare protection. In Terraform, the cloudflare_ip_ranges data source supplies those ranges, which keeps the list current when Cloudflare changes them. Verify the lock by curling the ALB's DNS name from outside AWS. If it answers, the restriction is not working.
Read how we handle it in Cloud & Server Management.
Related Articles
What to Use on AWS When You Want ClickHouse Query Speed
AWS does not run ClickHouse as one of its own services, so fast analytics on AWS means ClickHouse Cloud, BYOC or a self-hosted cluster, or an AWS service that covers part of the job. This compares them with Redshift, Athena, OpenSearch and Timestream by workload, with vendor claims marked as such.
CloudHow to Set Up Budgets and Anomaly Detection Before the Bill Surprises You
Finding out about a spend problem from the invoice means finding out weeks late. AWS gives you two different mechanisms for catching it earlier, and they are not interchangeable. A budget fires when a number you chose is crossed, while Cost Anomaly Detection models what your spend normally looks like and tells you when the shape changes. This walks through setting up both from the CLI, the delay each one carries between the spend happening and the alert arriving, who should be on which notification, and the charges neither one will catch for you.
CloudHow to Run Multi AZ So It Actually Survives an AZ Failure
Most AWS accounts are multi AZ on paper already, and then a zone has a bad day and the site goes down anyway. Spreading a deployment across zones and keeping it serving when one disappears are two different properties. This covers what the managed services really do during a zone failure, including which failovers reset every open connection and how long each one takes, the single points that quietly survive a multi AZ design, and the commands to rehearse all of it on purpose.