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
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.
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.
CloudInfrastructure as Code: Terraform vs Pulumi
Compare Terraform and Pulumi for infrastructure as code with real-world examples, state management, testing strategies, and migration considerations.