Preserving query string during rewrite?

Huh. Yes, you are right. There is different behaviour at play.

The reason appears to be here:

	# Add trailing slash for directory requests
	@canonicalPath {
		file {path}/index.php
		not path */
	}
	redir @canonicalPath {http.request.orig_uri.path}/ 308
  • The first section deals with canonicalizing the request path. The goal is to ensure that requests that target a directory on disk actually have the trailing slash / added to the request path, so that only a single URL is valid for requests to that directory.
    This is performed by using a request matcher that matches only requests that don’t end in a slash, and which map to a directory on disk which contains an index.php file, and if it matches, performs a HTTP 308 redirect with the trailing slash appended. So for example, it would redirect a request with path /foo to /foo/ (appending a /, to canonicalize the path to the directory), if /foo/index.php exists on disk.

php_fastcgi (Caddyfile directive) — Caddy Documentation

But that redirect is not query-preserving. See here:

~/Projects/caddy
➜ curl -i 'https://localhost/add?query=foo'
HTTP/2 308
alt-svc: h3=":443"; ma=2592000
location: /add/
server: Caddy
content-length: 0
date: Thu, 28 Nov 2024 02:29:57 GMT

I am not sure why this is the case - possibly there’s a good reason for it, possibly it’s an oversight.

Instead of php_fastcgi you will have to copy and use the entire expanded form so that you can edit the canonical redirect. For your use case it should instead read:

redir @canonicalPath {http.request.orig_uri.path}/?{query} 308

I tested it like so and had a good result from /add?query=foo:

{
  debug
}

localhost {
  route {
    # Add trailing slash for directory requests
    @canonicalPath {
      file {path}/index.php
      not path */
    }
    redir @canonicalPath {http.request.orig_uri.path}/?{query} 308

    # If the requested file does not exist, try index files
    @indexFiles file {
      try_files {path} {path}/index.php index.php
      split_path .php
    }
    rewrite @indexFiles {file_match.relative}

    # Proxy PHP files to the FastCGI responder
    @phpFiles path *.php
    reverse_proxy @phpFiles unix//run/php-fpm/www.sock {
      transport fastcgi {
      	split .php
      }
    }
  }
}
~/Projects/caddy
➜ curl -i 'https://localhost/add?query=foo'
HTTP/2 308
alt-svc: h3=":443"; ma=2592000
location: /add/?query=foo
server: Caddy
content-length: 0
date: Thu, 28 Nov 2024 02:27:41 GMT
{
  "AUTH_TYPE": "",
  "DOCUMENT_URI": "add/index.php",
  "SCRIPT_NAME": "/add/index.php",
  "HTTPS": "on",
  "CONTENT_LENGTH": "",
  "REMOTE_PORT": "62283",
  "SCRIPT_FILENAME": "/Users/whitestrake/Projects/caddy/add/index.php",
  "HTTP_X_FORWARDED_HOST": "localhost",
  "HTTP_HOST": "localhost",
  "SERVER_SOFTWARE": "Caddy/v2.8.4",
  "DOCUMENT_ROOT": "/Users/whitestrake/Projects/caddy",
  "REQUEST_URI": "/add/?query=foo",
  "HTTP_X_FORWARDED_FOR": "::1",
  "QUERY_STRING": "query=foo",
  "REMOTE_USER": "",
  "REQUEST_SCHEME": "https",
  "SERVER_NAME": "localhost",
  "REMOTE_ADDR": "::1",
  "HTTP_X_FORWARDED_PROTO": "https",
  "HTTP_USER_AGENT": "curl/8.10.1",
  "REMOTE_IDENT": "",
  "REQUEST_METHOD": "GET",
  "SERVER_PROTOCOL": "HTTP/2.0",
  "SSL_PROTOCOL": "TLSv1.3",
  "SSL_CIPHER": "TLS_AES_128_GCM_SHA256",
  "CONTENT_TYPE": "",
  "PATH_INFO": "",
  "REMOTE_HOST": "::1",
  "SERVER_PORT": "443",
  "GATEWAY_INTERFACE": "CGI/1.1",
  "HTTP_ACCEPT": "*/*"
}
1 Like