Caddy Traffic Capture

This guide shows you how to use Qtap to capture HTTP traffic flowing through Caddy, a modern web server with automatic HTTPS. You'll learn how to observe both incoming client requests and outgoing upstream connections from your Caddy server, all without proxies or code changes.

What You'll Learn

  • Capture Caddy ingress traffic (client requests)

  • Capture Caddy egress traffic (upstream service requests)

  • Monitor both sides of a reverse proxy simultaneously

  • Apply conditional capture rules for specific routes

  • Handle Caddy's automatic HTTPS with Qtap's TLS inspection

  • Set up Caddy + Qtap in Docker for testing

  • Deploy production-ready configurations

Use Cases

Why capture Caddy traffic?

  • Reverse Proxy Visibility: See both client requests and backend responses

  • API Gateway Monitoring: Track all API calls through your Caddy gateway

  • Automatic HTTPS Inspection: See inside TLS traffic without managing certificates

  • Microservices Debugging: Debug issues between services

  • Performance Analysis: Measure latency at each hop

  • Security Auditing: Monitor for suspicious traffic patterns

  • Migration Planning: Understand traffic patterns before infrastructure changes


Prerequisites

  • Linux system with kernel 5.10+ and eBPF support

  • Docker installed (for this guide's examples)

  • Root/sudo access

  • Basic understanding of Caddy/Caddyfile syntax


Part 1: Simple Caddy Web Server

Let's start with a basic Caddy setup serving static content and reverse proxying to upstream services.

Step 1: Create Caddy Configuration

Create a directory for our demo:

Create Caddyfile:

Step 2: Create Qtap Configuration

Create qtap.yaml:

Key Configuration Points:

  • direction: all - Captures both client→caddy AND caddy→upstream traffic

  • ignore_loopback: false - Important! Caddy often uses localhost

  • level: full - Captures complete requests/responses including bodies

Step 3: Create Docker Compose Setup

Create docker-compose.yaml:


Part 2: Running and Testing

Step 1: Start the Services

Step 2: Generate Test Traffic

Step 3: View Captured Traffic

What you should see:

Key indicators that it's working:

  • "exe": "/usr/bin/caddy" or similar - Caddy process identified

  • Direction: INGRESS - Client to Caddy

  • Direction: EGRESS - Caddy to upstream

  • Two transactions for proxied requests (ingress + egress)

  • ✅ Custom headers visible (X-Request-ID, X-Forwarded-Server)

  • ✅ Full request/response bodies captured

  • ✅ Latency tracked for both hops


Part 3: Advanced Configurations

Configuration 1: Capture Only Errors

Reduce volume by capturing only failed requests:

Test it:

Configuration 2: Route-Specific Capture

Capture different levels for different Caddy routes using Rulekit:

Configuration 3: HTTPS Upstream Monitoring

When Caddy proxies to HTTPS upstreams, Qtap can still see the traffic:

Caddyfile:

qtap.yaml:

Why this works: Qtap hooks into Caddy's TLS library (typically Go's crypto/tls) before encryption happens, so it sees plaintext even for HTTPS upstreams.

Configuration 4: Production Setup with S3

For production, store sensitive data securely:

Update docker-compose.yaml:


Part 4: Real-World Use Cases

Use Case 1: API Gateway with Authentication

Monitor API gateway with focus on authentication and errors:

Caddyfile:

qtap.yaml:

Use Case 2: Microservices Mesh Monitoring

Monitor Caddy as a service mesh proxy:

Caddyfile:

qtap.yaml:

Use Case 3: Static Site with CDN Backend

Monitor Caddy serving static sites with CDN backend:

Caddyfile:

qtap.yaml:


Understanding the Output

Dual Capture for Reverse Proxy

When Caddy proxies a request, Qtap captures two HTTP transactions:

Transaction 1: INGRESS (Client → Caddy)

Transaction 2: EGRESS (Caddy → Upstream)

This lets you:

  • Measure total latency vs. backend latency

  • See how Caddy transforms requests (headers, paths)

  • Debug issues on either side of the proxy

Caddy-Specific Details

Process Identification:

  • Look for exe containing caddy (often /usr/bin/caddy or /usr/local/bin/caddy)

  • Container name: caddy-demo (in Docker)

Automatic HTTPS:

  • When Caddy uses automatic HTTPS, Qtap still sees plaintext via eBPF TLS hooks

  • No certificate management needed

  • Works with Let's Encrypt, ZeroSSL, or custom CAs


Troubleshooting

Not Seeing Caddy Traffic?

Check 1: Is Qtap running before requests?

Check 2: Is ignore_loopback correct?

Check 3: Is Caddy processing requests?

Check 4: Verify Qtap hooks Caddy

Seeing "l7Protocol": "other"?

This means connection captured but HTTP not parsed:

  • Wait longer after starting Qtap (6+ seconds)

  • Check if Caddy is using HTTP/3 (QUIC) - not yet supported

  • Verify traffic is actually HTTP/HTTPS

Caddy Using HTTP/3?

Qtap currently supports HTTP/1.x and HTTP/2. If Caddy negotiates HTTP/3 (QUIC):

Disable HTTP/3 in Caddyfile:

Too Much Traffic?

Option 1: Conditional capture

Option 2: Filter specific routes

Option 3: Summary level only


Performance Considerations

Caddy + Qtap Performance

Qtap operates out-of-band with minimal overhead:

  • CPU: ~1-3% for typical traffic

  • Memory: ~50-200MB depending on volume

  • Latency: Zero additional latency (passive observation)

Best practices for high-traffic Caddy:

  1. Use level: summary or details for high volume

  2. Apply conditional rules to capture selectively

  3. Filter health checks and monitoring endpoints

  4. Send to S3 with batching (use Fluent Bit)

  5. Set TTL policies on storage (90 days recommended)

Scaling Recommendations

Traffic Volume

Recommended Level

Storage

< 100 req/sec

full

stdout or S3

100-1000 req/sec

details

S3 with batching

1000-10000 req/sec

summary

S3 + Fluent Bit

> 10000 req/sec

conditional rules

S3 + Fluent Bit + aggressive filtering


Caddy vs NGINX: Key Differences

Process Name:

  • Caddy: /usr/bin/caddy

  • NGINX: /usr/sbin/nginx

Configuration:

  • Caddy: Caddyfile (simpler, more human-readable)

  • NGINX: nginx.conf (more complex, more options)

HTTPS:

  • Caddy: Automatic by default (Qtap still works!)

  • NGINX: Manual configuration

Language:

  • Caddy: Written in Go (uses Go's crypto/tls)

  • NGINX: Written in C (uses OpenSSL)

Both work perfectly with Qtap's eBPF-based capture.


Next Steps

Learn More About Qtap:

Production Deployment:

Related Guides:

Alternative: Cloud Management:


Cleanup


This guide uses validated configurations. All examples are tested and guaranteed to work with Caddy and Qtap.

Last updated