Caddy’s rewrite and redirect directives don’t just change URLs; they fundamentally alter Caddy’s internal request processing loop, allowing you to dynamically control how requests are handled based on their characteristics.
Let’s see this in action. Imagine you have a static site and want to redirect all http traffic to https, and also ensure that www.example.com always goes to example.com.
example.com {
redir https://{host}{uri}
redir https://www.example.com {
to https://example.com{uri}
permanent
}
# ... your static site handler ...
file_server
}
When a request comes in for http://www.example.com/about, Caddy first checks the redir https://{host}{uri}. Since the host is www.example.com and the scheme is http, this condition is met. Caddy generates a 301 Permanent Redirect to https://www.example.com/about and stops processing further directives for this request.
If the request was for https://www.example.com/about, the first redir would be skipped because the scheme is already https. Caddy would then evaluate the second redir. The host www.example.com matches, so Caddy generates a 301 Permanent Redirect to https://example.com/about and stops processing.
If the request was for https://example.com/about, neither redir directive would match, and Caddy would proceed to the file_server directive, serving the about page from your static site.
The magic here lies in how Caddy processes these directives. When a redir directive matches, Caddy doesn’t just inject a new URL; it rewrites the internal request object. This includes changing the URI, the host, and even the scheme. Crucially, once a redir directive matches and issues a redirect response, Caddy stops processing subsequent directives within that block for that request. This is why the order matters and why you can chain them to create sophisticated routing logic.
Behind the scenes, Caddy uses a concept called the "request matcher" system. Directives like redir and rewrite have associated matchers (implicit or explicit) that determine if they should be executed. When a request enters Caddy, it iterates through the directives in your configuration. For each directive, it checks its matchers. If the matchers pass, the directive’s action is performed. For redir, this action is to send a redirect response. For rewrite, it’s to modify the request’s URI and then continue processing other directives in the same block.
The rewrite directive is similar but doesn’t send a redirect response. Instead, it modifies the request’s URI and then Caddy continues processing the remaining directives within the current site block. This is incredibly powerful for internal routing. For example, you might want to rewrite all requests for /api/* to a specific backend service.
example.com {
rewrite /api/* /internal/api/{1}
reverse_proxy /internal/api/* backend-service:8080
}
Here, a request for /api/users would be rewritten internally to /internal/api/users. Caddy doesn’t send a redirect. It then continues processing, and the reverse_proxy directive, which also has a matcher, now sees the URI as /internal/api/users and forwards it to backend-service:8080.
The to subdirective in redir is where the actual URL construction happens. You can use placeholders like {host}, {scheme}, {uri}, and captured groups from regular expressions in matchers to dynamically build the target URL. This allows for complex URL transformations, like canonicalizing URLs or mapping old URLs to new ones.
A common pitfall is misunderstanding the order of operations. If you have multiple rewrite directives, Caddy processes them in the order they appear. If a rewrite directive changes the URI, subsequent rewrite directives in the same block will operate on that new URI. This can be a source of confusion if not carefully managed. Also, remember that redir terminates the request processing for that block, while rewrite allows it to continue.
The handle and handle_path directives offer a more structured way to group directives, and matchers can be applied at the handle level. This allows you to create distinct processing pipelines for different types of requests. For instance, you could have one handle block for API requests and another for static assets, each with its own set of rewrite or redir rules.
When dealing with complex rewrite and redirect scenarios, it’s easy to get caught in infinite redirect loops. This often happens when a rewrite directive changes the URL in a way that causes a preceding or subsequent redir directive to match again, creating a cycle. Carefully reviewing the order of directives and the exact URL transformations is key to avoiding this.
The redir directive can also take an optional status code, defaulting to 302 Found. You can explicitly set it to 301 Permanent for SEO benefits or 307 Temporary if the redirect is transient. This explicit control over the HTTP status code is vital for correct client and search engine behavior.
Next, you’ll likely encounter the need to conditionally apply rewrites or redirects based on request headers or query parameters, which leads into Caddy’s advanced matcher syntax.