The situation
The environment:
- Roughly 200 EC2 instances and ECS tasks across 5 production VPCs, each needing outbound HTTPS to a curated list of destinations: an artifact registry, a cloud vendor’s APIs, the monitoring vendor, a handful of third-party API providers.
- Each workload authenticates to specific endpoints; many use bearer tokens in
Authorizationheaders. - Compliance wants: “prove you cannot exfiltrate data to a destination not on the allowlist,” with monthly evidence. They also want the ability to ban entire URL paths (not just hostnames) on certain destinations.
- Operations wants: minimal latency impact, minimal new infrastructure to run, good audit logs.
Options:
- AWS Network Firewall with domain-list rules (SNI matching), per-destination allowlist.
- Squid forward proxy fleet. HTTPS CONNECT for opaque tunnelling, optional MITM for full inspection.
- Third-party proxy appliance (ZScaler, Umbrella, McAfee, etc.) deployed as EC2 or SaaS.
- Combined approach. Network Firewall for broad deny-by-default, Squid behind it for URL-path rules on specific destinations.
What actually matters
Before choosing, it’s worth understanding what each layer of inspection can actually see.
Layer 3-5 (5-tuple + TLS metadata) reads:
- The 5-tuple: source IP, destination IP, protocol, source and destination ports.
- TLS SNI from the ClientHello (layer-5 metadata in the first client-side TLS record).
- HTTP headers including Host (for unencrypted HTTP).
- TLS handshake fingerprints if enabled.
It does not read:
- Encrypted TLS payload.
- The URL path (in HTTPS; it sees SNI for the hostname, but everything after
https://example.com/is encrypted). - The HTTP method, headers, body of an HTTPS request.
- The response body.
Layer 7 (terminating proxy) reads:
- Everything the lower layers read, plus:
- The full URL path on
HTTP CONNECT, but only the host and port, the proxy-established tunnel is opaque payload after. - For TLS-terminating mode (proxy terminates, re-originates with its own cert chain that the client trusts): every header, URL, method, body, response, essentially everything in the HTTP exchange. Comes with a real trade-off: the proxy’s CA cert must be trusted by every client; certificate-pinning workloads break; privacy implications are real; decrypting TLS has performance cost.
Which layer matches the requirement?
If the allowlist is “these hostnames only,” SNI-level inspection is sufficient. SNI matching enforces that only TLS connections to allowed hostnames complete the handshake. Blocked connections are logged with source, destination, SNI, and rule.
If the allowlist is “these hostnames, and for some of them only these URL paths,” no SNI-level layer can enforce URL-path rules on HTTPS because the path is encrypted. A terminating proxy can; a hostname allowlist cannot.
If the workloads use certificate pinning (which security-conscious SDKs increasingly do), TLS termination breaks them. You’re limited to CONNECT-style proxying, which sees the hostname and port but nothing inside.
If the concern is “data exfiltration through legitimate destinations”, someone using an allowlisted service to upload stolen data, only full-payload inspection catches that, and only where pinning doesn’t intervene.
What we’ll filter on
- Hostname allowlisting, block by destination name, not IP?
- URL-path-level policy, deny
POST /secreton an allowed host? - Works with certificate-pinned clients, no MITM required?
- Operational burden, how much infrastructure to run?
- Audit trail depth, how rich is the log record?
The egress-filter landscape
1. AWS Network Firewall. Managed, sees SNI, blocks by hostname. Can’t see URL paths for HTTPS. Strong on low ops, partial on policy granularity.
2. Squid CONNECT proxy. Sees destination hostname:port from the CONNECT method, tunnels the rest opaquely. Logs destination and bytes. Clients must be configured with HTTPS_PROXY. Equivalent SNI-layer policy; more configurable; operational burden.
3. Squid MITM proxy (or any vendor proxy with TLS inspection). Terminates TLS, inspects everything, re-originates with a proxy CA. Full URL-path and header policy. Breaks certificate pinning. Privacy and trust implications. Higher throughput cost.
4. Third-party proxy (SaaS or vendor appliance). ZScaler, Umbrella, etc. Usually CONNECT-based for privacy reasons; some offer TLS inspection. SaaS reduces AWS-side operations but introduces a vendor dependency.
5. Combined Network Firewall + proxy. Network Firewall for broad deny-by-default; proxy fleet behind it for URL-path rules on selected destinations.
Side by side
| Option | Hostname allowlisting | URL-path policy | Works with cert pinning | Ops burden | Audit trail |
|---|---|---|---|---|---|
| Network Firewall | ✓ (SNI) | ✗ (HTTPS) | ✓ | Low | Good (SNI, 5-tuple) |
| Squid CONNECT | ✓ (Host:Port) | ✗ (HTTPS) | ✓ | Medium | Good (Host, bytes) |
| Squid MITM | ✓ | ✓ | ✗ | Medium-high | Excellent (full HTTP) |
| Third-party SaaS | ✓ | Depends | Depends | Low (AWS-side) | Vendor-specific |
| Combined NFW + proxy | ✓ | ✓ (via proxy) | ✓ (if proxy is CONNECT) | Medium | Layered logs |
Reading by requirement:
- “Block by hostname, log the blocks, don’t break anything” → Network Firewall.
- “Block by URL path too, and be willing to run a proxy fleet” → Squid MITM (if pinning isn’t an issue) or combined NFW+proxy.
- “Don’t want to run infrastructure; pay vendor” → SaaS third-party.
Two layers, two views
The pick(s) in depth
Network Firewall first. Forward proxy only if URL-path policy is genuinely required. The shape that matches most compliance requirements without the MITM footprint.
Network Firewall setup was covered extensively in the stateful-rules post earlier in this series; the short version for egress filtering:
- Deploy Network Firewall in the egress VPC; route workload VPCs through it.
- Stateful rule group with a domain allowlist (
pass tlsrules for each allowed SNI, a default drop at the bottom). - Alert logs to CloudWatch; flow logs to S3.
- Per-environment policies if prod and non-prod have different allowlists.
Forward proxy setup (Squid, in explicit mode):
- Deploy a Squid fleet on EC2 (or containers) behind an NLB in the egress VPC.
- Workload clients set
HTTPS_PROXY=http://proxy.internal:3128in their environment. - Squid config:
acl allowlisted_hosts dstdomain api.github.com registry.npmjs.org ... http_access allow allowlisted_hosts http_access deny all access_log /var/log/squid/access.log squid - For CONNECT mode (TLS tunnelling with no decryption), the above is sufficient. Squid sees the Host and Port from the CONNECT request and either permits the tunnel or denies.
- For MITM mode (to see URL paths), add
ssl_bump peek/bumpconfiguration plus a proxy CA that clients trust. Requires careful consideration of certificate pinning, privacy, and operational complexity.
Combined approach, the practical best-of-both:
- Network Firewall as the default-deny egress at the VPC egress point. Allowlist all destinations that clients need.
- A proxy fleet for the subset of destinations where URL-path policy is required. Clients that need path-level policy are configured with
HTTPS_PROXYpointing at the proxy; others go direct through Network Firewall. - This layers the defences: a rogue process that doesn’t honour
HTTPS_PROXYstill hits Network Firewall’s SNI-based allowlist and gets blocked if its destination isn’t approved.
What about the auth headers in logs? A MITM proxy sees Authorization: bearer xxx headers in decrypted requests. Logging them verbatim is a problem. Squid with access_log by default does not log request bodies or headers beyond URL, but the request URL can contain secrets (query-string tokens). Audit logging policy for a MITM proxy needs explicit thought: what fields are captured, what’s redacted, who can read the logs.
A worked comparison
A workload in production tries to POST /api/exfiltrate to api.allowlisted-saas.com, an allowed destination:
Network Firewall path:
- TCP SYN to
api.allowlisted-saas.com:443hits Network Firewall. - ClientHello arrives with SNI
api.allowlisted-saas.com. Rule matches; pass. - HTTPS request proceeds opaquely. Network Firewall sees bytes flowing; doesn’t see POST, path, or body.
- Logs show: source, destination, SNI, pass. Not blocked.
MITM Forward Proxy path (if the client uses it):
- Client sends HTTP CONNECT to proxy.
- Proxy sees CONNECT
api.allowlisted-saas.com:443. For MITM mode, proxy intercepts; for CONNECT-only mode, proxy tunnels. - In MITM mode, proxy terminates TLS, sees
POST /api/exfiltrate. Rule matches “deny POST /api/* on this host.” Block. - Logs show: user, method, path, action. Blocked.
For exfiltration detection, the proxy wins. For blocking based on allowlist only, Network Firewall is equivalent and simpler.
What’s worth remembering
- Network Firewall sees SNI; a forward proxy in MITM sees everything. If the policy is “allow these hostnames,” Network Firewall suffices. If the policy is “allow these URL paths,” only a MITM proxy (or unencrypted HTTP) can enforce.
- SNI inspection does not decrypt. The SNI is unencrypted metadata in the ClientHello. Network Firewall reads it and matches against the allowlist without any crypto intervention.
- MITM breaks certificate pinning. Clients that pin the real destination’s certificate cannot tolerate a proxy presenting a different one. Common in security-sensitive SDKs; increasing over time. Evaluate before committing to MITM.
- CONNECT-mode proxies are equivalent to Network Firewall’s SNI for visibility. Both see the hostname; neither sees inside the TLS session. Operational choice, not a visibility difference.
- Proxies require client configuration. Workloads need
HTTPS_PROXYor equivalent; Network Firewall is transparent to the client. Transparency is an operational win; control is a trade. - Layering them is the realistic answer in regulated environments. Network Firewall for the broad allowlist; proxy for path-level policy on specific destinations; logging from both feeding the SOC.
- Audit log content differs. Network Firewall logs 5-tuple + SNI + rule. Proxy logs 5-tuple + method + URL + sometimes headers. Both to CloudWatch / S3 / Firehose.
- Third-party SaaS shifts the operational burden to a vendor. ZScaler, Umbrella, etc. offload the proxy fleet; they add a dependency and an egress hop that may affect latency.
“Block outbound traffic we don’t want” is a simple sentence with two very different technical answers depending on the layer we inspect at. Network Firewall at SNI-layer is cheaper to run and never breaks a client. Forward proxy at L7 sees everything but demands client configuration and can break certificate-pinned clients if it MITMs. The work isn’t picking the fanciest option, it’s matching the inspection layer to the granularity of the policy, and layering when neither layer alone is enough.