Caddy can dynamically transform your HTTP traffic by adding, removing, or rewriting headers, acting as a highly configurable proxy.
Let’s see it in action. Imagine you have a backend service that doesn’t set a X-Powered-By header, but you want to add it for debugging purposes. Caddy can do this seamlessly.
Here’s a Caddyfile snippet:
:8080 {
reverse_proxy backend.example.com {
header_up X-Forwarded-For {remote_ip}
header_up X-Real-IP {remote_ip}
header_up X-Powered-By Caddy
}
}
When a request hits Caddy on port 8080, it forwards it to backend.example.com. Before sending the request, Caddy adds three headers: X-Forwarded-For and X-Real-IP with the client’s IP address, and X-Powered-By with the value Caddy.
If the backend did set a X-Powered-By header, Caddy’s header_up directive would add a second X-Powered-By header. This is generally not what you want if you’re trying to set a specific header. To replace an existing header, you’d use header_up with the same name. Caddy processes directives in the order they appear.
Now, let’s say your backend is setting a Server header, and you want to hide that information from the client for security reasons. You can remove it using header_down:
:8080 {
reverse_proxy backend.example.com {
header_down -Server
}
}
The hyphen (-) prefix before Server tells Caddy to remove any Server header present in the response from backend.example.com before sending it back to the client.
What if you want to rewrite a header? Perhaps you want to standardize a Host header sent by a client that might be sending variations. Or maybe you want to append a value to an existing header.
Consider this example:
:8080 {
reverse_proxy backend.example.com {
header_up Host {host}
header_up X-Request-ID {request.id}
header_up X-Client-Version "v1.2.3"
}
}
Here, header_up Host {host} ensures that the Host header sent to the backend is exactly what the client requested. header_up X-Request-ID {request.id} injects Caddy’s internal request ID into the request, useful for tracing. header_up X-Client-Version "v1.2.3" adds a static version string.
The header_up directive modifies headers sent to the upstream server. The header_down directive modifies headers sent from the upstream server back to the client.
You can also use Caddy’s powerful placeholders within header values. Some common ones include:
{remote_ip}: The IP address of the client.{host}: The requested host name.{uri.path}: The path part of the request URI.{request.id}: A unique ID for the current request.{time}: The current time.{upstream.host}: The host name of the upstream server.
You can also combine multiple header directives. Caddy processes them in order. For instance, you could add a header, then modify it.
:8080 {
reverse_proxy backend.example.com {
header_up X-Original-Host {host}
header_up Host backend.internal.net # Overwrites whatever was there before
}
}
This first adds X-Original-Host with the client’s requested host, and then overwrites the Host header with a fixed internal value.
A less commonly known, but very powerful, feature is the ability to use regular expressions for more complex header manipulation. This is done using the header_regex directive. It allows you to match and replace parts of header values.
For example, if you wanted to sanitize a User-Agent header, removing specific potentially sensitive version information, you could do something like this:
:8080 {
reverse_proxy backend.example.com {
header_down "User-Agent" {header_down "User-Agent" |re.ReplaceAllString "specific-version/[0-9.]+" ""}
}
}
This snippet takes the existing User-Agent header from the response, applies a regular expression to remove any occurrences of specific-version/ followed by digits and dots, and then sets the modified header back. The |re.ReplaceAllString part is Caddy’s syntax for applying a regular expression transformation.
This mechanism of header manipulation is fundamental to how Caddy acts as a sophisticated API gateway, security layer, or traffic shaper, allowing you to fine-tune the HTTP communication without touching your backend applications.
The next step in advanced header control involves using named matchers to apply header directives only to specific requests based on complex criteria.