Learning Caddy - URI Manipulation & Rewriting
Episode 12 of 31

Learning Caddy - URI Manipulation & Rewriting

This episode covers URI manipulation: the rewrite and uri directives, stripping and adding prefixes, clean URLs, variables and placeholders, regex capture groups, and try_files patterns for SPA and PHP routing.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Not every incoming URL is perfect. Users open /produk while the file is named /produk.html. An old API uses /v1 while the new one uses /v2. An SPA routes everything through index.html. All of this is handled with URI manipulation — and episode 12 is where you learn it.

The rewrite Directive

Basic Path Rewriting

rewrite changes the path that handlers process, without changing the URL in the browser:

Basic path rewrite
example.com {
    rewrite /produk /produk.html
    root * /var/www
    file_server
}

A request to /produk is changed to /produk.html internally before file_server looks for the file. The browser still shows /produk in the address bar — clean for SEO.

Stripping and Adding Prefixes

Removing a prefix is a common reverse proxy pattern:

Strip the /api prefix
example.com {
    rewrite /api/* /api{path}
    reverse_proxy localhost:8080
}

Actually, the more common pattern is stripping the prefix before reaching the backend. Example using regex:

Remove the prefix with regex
example.com {
    @api path /api/*
    handle @api {
        uri strip_prefix /api
        reverse_proxy localhost:8080
    }
}

uri strip_prefix /api removes /api from the path. So /api/users becomes /users when forwarded to the backend — the backend doesn't need to know about the prefix.

The uri Directive

Query String Manipulation

uri can add, change, or remove query parameters:

Query string manipulation
example.com {
    @tracked query campaign=*
    handle @tracked {
        uri query_upd utm_source caddy
        reverse_proxy localhost:8080
    }
}

uri query_upd utm_source caddy adds or updates the utm_source=caddy parameter. Useful for campaign tracking before the request is forwarded.

Path Normalization

uri can also normalize paths — for example, cleaning up double dots:

Path normalization
example.com {
    uri path_clean
    root * /var/www
    file_server
}

uri path_clean normalizes segments like /a/../b to /b. This prevents path traversal.

Variables and Placeholders

Request Variables

Caddy provides placeholders you can use in directives:

Placeholder in root
*.example.com {
    root * /var/www/sites/{host}
    file_server
}

Common placeholders you should know:

  • {host} — the host from the request.
  • {path} — the path without the query.
  • {query} — the query string.
  • {method} — the request method.
  • {remote_host} — the client IP.

root * /var/www/sites/{host} uses the {host} placeholder to pick a directory per subdomain — a static multi-tenant pattern.

Regex Capture Groups

The path_regexp matcher can capture parts of the path and reuse them:

Capture group for routing
example.com {
    @artikel path_regexp ^/blog/([a-z0-9-]+)
    handle @artikel {
        rewrite /blog/{re.1} /posts/{re.1}.html
        root * /var/www
        file_server
    }
}

rewrite /blog/{re.1} /posts/{re.1}.html uses {re.1}, the first capture group from the regex — the article name captured from the URL. This pattern maps clean URLs to hidden physical files.

try_files for Routing

SPA Fallback

The most common pattern for React and Vue applications:

SPA fallback to index.html
app.example.com {
    root * /app/dist
    try_files {path} /index.html
    file_server
}

try_files {path} /index.html tries the file matching the path; if it doesn't exist, it falls back to index.html so the SPA router works. We touched this in episode 5 — here you see the reason from the URI side.

Custom 404 and PHP Routing

Combine the fallback with a dedicated page:

try_files with a 404 page
example.com {
    root * /var/www
    try_files {path} {path}.html /404.html
    file_server
}

If neither the file nor the .html version exists, Caddy serves 404.html with status 404. For PHP, the pattern becomes:

Fallback for PHP
example.com {
    root * /var/www
    php_fastcgi unix//run/php/php-fpm.sock
    try_files {path} index.php?{query}
}

try_files {path} index.php?{query} routes every request that doesn't match a file to index.php — the front controller pattern used by Laravel and Symfony.

Conclusion

Episode 12 completed your routing arsenal: rewrite for internal path changes, uri for prefix stripping and query manipulation, variables and placeholders like {host}, {path}, and {query}, regex capture groups with {re.1}, and try_files patterns for SPAs, 404 pages, and PHP front controllers.

Key takeaways:

  • rewrite changes the internal path without changing the browser URL.
  • uri strip_prefix removes a prefix before forwarding to the backend.
  • Placeholders {host}, {path}, {query} are available throughout the configuration.
  • Regex capture groups are used via {re.1}.
  • try_files {path} /index.html is the standard SPA pattern.
  • PHP front controllers use try_files {path} index.php?{query}.

In the next episode, episode 13, we'll cover redirects & error handling — the redir directive with all its status codes, common patterns like www to non-www and old URL redirects, custom error pages with handle_errors, and the respond directive for static and JSON responses.

Learning Caddy - URI Manipulation & Rewriting | Learning Caddy