Create file with PHP if not exist in cache

1. The problem I’m having:

I am trying to switch my personal server from apache to Caddy. I have solved a number of surprising issues, sometimes by changing the services running instead of the config, but this one has me stumped.

How can I convert this apache directive

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^cache/(.*).ico favicon.php?$1

to Caddyfile syntax? For those unfamiliar with Apache, this says “serve the file in /cache/foo.ico if it exists, and if not, execute favicon.php?foo”.

try_files seemed the most relevant, and worked earlier, but here it serves the php file instead of executing it, and I’m not quite seeing how to use path_regexp as a component of other tools; in particular, how else to check for a file.

2. Error messages and/or full log output:

2024/12/22 22:41:43.924	INFO	using config from file	{"file": "/etc/caddy/Caddyfile"}
2024/12/22 22:41:43.932	INFO	adapted config to JSON	{"adapter": "caddyfile"}
2024/12/22 22:41:43.935	INFO	admin	admin endpoint started	{"address": "unix//run/caddy/admin.socket", "enforce_origin": false, "origins": ["", "//127.0.0.1", "//::1"]}
2024/12/22 22:41:43.936	INFO	tls.cache.maintenance	started background certificate maintenance	{"cache": "0xc000898e80"}
2024/12/22 22:41:43.936	DEBUG	http.auto_https	adjusted config	{"tls": {"automation":{"policies":[{}]}}, "http": {"servers":{"srv0":{"listen":[":8043"],"routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"vars","root":"/srv/http/iiridayn.info/www"}]},{"handle":[{"handler":"static_response","headers":{"Location":["/filk/"]},"status_code":302}],"match":[{"path":["/filk"]}]},{"group":"group1","handle":[{"handler":"rewrite","uri":"{http.matchers.file.relative}"}],"match":[{"file":{"try_files":["@icon"]}}]},{"group":"group1","handle":[{"handler":"rewrite","uri":"{http.matchers.file.relative}?{http.regexp.icon.1}"}],"match":[{"file":{"try_files":["/favicon.php"]}}]},{"group":"group5","handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"rewrite","strip_path_prefix":"/filk"}]},{"group":"group2","handle":[{"handler":"rewrite","uri":"{http.matchers.file.relative}"}],"match":[{"file":{"try_files":["{http.request.uri.path}"]}}]},{"group":"group2","handle":[{"handler":"rewrite","uri":"{http.matchers.file.relative}?{http.request.uri}\u0026{http.request.uri.query}"}],"match":[{"file":{"try_files":["filk/dumbwiki.php"]}}]}]}],"match":[{"path":["/filk/*"]}]},{"group":"group5","handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"rewrite","strip_path_prefix":"/api"}]},{"group":"group0","handle":[{"handler":"rewrite","uri":"api.php/{http.request.uri}"}]}]}],"match":[{"path":["/api/*"]}]},{"group":"group5","handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"file_server","hide":["/etc/caddy/Caddyfile"]}]}]}]},{"handle":[{"handler":"static_response","headers":{"Location":["{http.request.orig_uri.path}/"]},"status_code":308}],"match":[{"file":{"try_files":["{http.request.uri.path}/index.php"]},"not":[{"path":["*/"]}]}]},{"handle":[{"handler":"rewrite","uri":"{http.matchers.file.relative}"}],"match":[{"file":{"split_path":[".php"],"try_files":["{http.request.uri.path}","{http.request.uri.path}/index.php","index.php"]}}]},{"handle":[{"handler":"reverse_proxy","transport":{"protocol":"fastcgi","split_path":[".php"]},"upstreams":[{"dial":"unix//run/php-fpm/caddy.sock"}]}],"match":[{"path":["*.php"]}]}]}],"terminal":true}],"automatic_https":{"skip":["iiridayn.info"]}}}}}
2024/12/22 22:41:43.940	DEBUG	http	starting server loop	{"address": "[::]:8043", "tls": false, "http3": false}
2024/12/22 22:41:43.940	INFO	http.log	server running	{"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2024/12/22 22:41:43.941	INFO	autosaved config (load with --resume flag)	{"file": "/root/.local/share/caddy/autosave.json"}
2024/12/22 22:41:43.941	INFO	serving initial configuration
2024/12/22 22:41:43.943	INFO	tls	storage cleaning happened too recently; skipping for now	{"storage": "FileStorage:/root/.local/share/caddy", "instance": "0df056e3-34d8-474b-997f-6e16dd3100db", "try_again": "2024/12/23 22:41:43.943", "try_again_in": 86399.999998003}
2024/12/22 22:41:43.944	INFO	tls	finished cleaning storage units
2024/12/22 22:41:48.112	DEBUG	http.handlers.rewrite	rewrote request	{"request": {"remote_ip": "fe80::daef:18ce:84aa:80e1%enp2s0", "remote_port": "52232", "client_ip": "fe80::daef:18ce:84aa:80e1", "proto": "HTTP/1.1", "method": "GET", "host": "iiridayn.info:8043", "uri": "/cache/example.com.ico", "headers": {"User-Agent": ["curl/8.6.0"], "Accept": ["*/*"]}}, "method": "GET", "uri": "/favicon.php?%3Cnil%3E"}
2024/12/22 22:41:48.112	DEBUG	http.handlers.file_server	sanitized path join	{"site_root": "/srv/http/iiridayn.info/www", "fs": "", "request_path": "/favicon.php", "result": "/srv/http/iiridayn.info/www/favicon.php"}
2024/12/22 22:41:48.112	DEBUG	http.handlers.file_server	opening file	{"filename": "/srv/http/iiridayn.info/www/favicon.php"}
^C2024/12/22 22:41:52.394	INFO	shutting down	{"signal": "SIGINT"}
2024/12/22 22:41:52.394	WARN	exiting; byeee!! 👋	{"signal": "SIGINT"}
2024/12/22 22:41:52.394	INFO	http	servers shutting down with eternal grace period
2024/12/22 22:41:52.395	INFO	admin	stopped previous server	{"address": "unix//run/caddy/admin.socket"}
2024/12/22 22:41:52.395	INFO	shutdown complete	{"signal": "SIGINT", "exit_code": 0}

3. Caddy version:

v2.8.4

4. How I installed and ran Caddy:

a. System environment:

OS: Arch Linux
Installed: pacman -S caddy
Server: Bare metal ZBOX-MA320 under my desk

b. Command:

caddy run --config /etc/caddy/Caddyfile

c. Service/unit/compose file:

d. My complete Caddy config:

# The Caddyfile is an easy way to configure your Caddy web server.
#
# https://caddyserver.com/docs/caddyfile
#
# The configuration below serves a welcome page over HTTP on port 80.
# To use your own domain name (with automatic HTTPS), first make
# sure your domain's A/AAAA DNS records are properly pointed to
# this machine's public IP, then replace the line below with your
# domain name.
#
# https://caddyserver.com/docs/caddyfile/concepts#addresses
{
        # Restrict the admin interface to a local unix file socket whose directory
        # is restricted to caddy:caddy. By default the TCP socket allows arbitrary
        # modification for any process and user that has access to the local
        # interface. If admin over TCP is turned on one should make sure
        # implications are well understood.
        admin "unix//run/caddy/admin.socket"
        debug
}

http://iiridayn.info:8043 {
        # Set this path to your site's directory.
        root * /srv/http/iiridayn.info/www

        php_fastcgi unix//run/php-fpm/caddy.sock

        handle_path /api/* {
                rewrite * api.php/{uri}
        }
        # if not exist          RewriteRule ^cache/(.*).ico favicon.php?$1
        @icon path_regexp /cache/(.*).ico$
        try_files @icon /favicon.php?{re.icon.1}

        redir /filk /filk/
        handle_path /filk/* {
                try_files {path} filk/dumbwiki.php?{uri}&{query}
                #if not exist RewriteRule ^(.*)$ dumbwiki.php?$1 [L,QSA,B,BNP]
        }

        handle {
                # Enable the static file server.
                file_server
        }

        # Another common task is to set up a reverse proxy:
        # reverse_proxy localhost:8080

        # Or serve a PHP site through php-fpm:
        # php_fastcgi localhost:9000

        # Refer to the directive documentation for more options.
        # https://caddyserver.com/docs/caddyfile/directives
}

# Import additional caddy config files in /etc/caddy/conf.d/
#import /etc/caddy/conf.d/*

5. Links to relevant resources:

https://caddyserver.com/docs/caddyfile/matchers#path-regexp
https://caddyserver.com/docs/caddyfile/directives/try_files
https://caddyserver.com/docs/caddyfile/directives/php_fastcgi
https://caddyserver.com/docs/caddyfile/directives/handle_path
https://httpd.apache.org/docs/2.4/rewrite/flags.html#flag_bnp
https://caddyserver.com/docs/caddyfile/matchers#path

Looks like http://iiridayn.info:8043/filk/index.html is trying to serve a blank file, so I don’t have the rest working right either. Didn’t notice that at first.