1. The problem I’m having:
Hi,
I’m sorry if I sound like a noob, because I’m one with respect to Caddy. I’m still experimenting around, trying to learn it. I want to restrict access to certain .php
files in an elegant manner. I’ve found many examples of people doing just that (see my Caddyfile), but I believe they all still leave a security hole or restrict too much.
If you look at my Caddyfile below, I have two snippets to return 403 for any request for certain files. The first one is for all future sites, the second one is solely for Wordpress installations (currently I have only one on my localhost). The (ban_global)
works as intended, forbidding any access to any .bak
and .swp
files. At the first look, the (ban_wordpress)
does too:
$ curl -I http://localhost/wp-config.php
HTTP/1.1 403 Forbidden
Content-Type: text/plain; charset=utf-8
Server: Caddy
Date: Thu, 03 Apr 2025 15:35:24 GMT
Content-Length: 9
However:
$ curl -I http://localhost/wp-config.php/qwerty
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Server: Caddy
Date: Thu, 03 Apr 2025 15:35:21 GMT
So the access is granted. Surely it’s because PHP requests are forwarded to php-fpm, which interprets /qwerty
as PATH_INFO. I know I can modify my path in Caddyfile to /wp-config.php*
, and it probably is the best fix in this particular case (as even if a file like wp-config.phpxyz
existed on my server, it probably shouldn’t be served anyway).
It still leaves a lingering question, though – what if I wanted to restrict access to a particular file (no matter what PATH_INFO added to the request), but still allow access to any file having its name as a prefix (like wp-config.phpxyz
mentioned)? In Apache I can restrict access on a file, not path basis. What is the simplest, most elegant way to achieve that in Caddy? Should I use path_regexp
? It sounds like a messy workaround and waste of CPU, while direct file access test would be more elegant.
2. Caddy version:
2.6.2
a. System environment:
Ubuntu 24.04 in a KVM/QEMU virtual machine.
b. My complete Caddy config:
(ban_global) {
@disallowed {
path *.bak *.swp
}
respond @disallowed "Forbidden" 403
}
(ban_wordpress) {
import ban_global
@disallowed_wp {
path /xmlrpc.php /wp-config.php
}
respond @disallowed_wp "Forbidden" 403
}
localhost:80 {
root * /var/www/wordpress0
import ban_wordpress
php_fastcgi unix//run/php/php8.3-fpm.sock
file_server
log {
output file /var/log/caddy/wordpress0.log
format console
}
}