V2: WordPress Caddyfile

WordPress is officially compatible with PHP 7.4 since WordPress 5.3 : Version 5.3 – WordPress.org Forums

I used PHP 7.4 before that and had no issue either. I don’t think it’s the problem for you.


I have try, and is working.
Please give it a short

www.yourdomian.com {
root * /usr/local/www/wordpress
log /var/log/caddy_log.log

        encode gzip

        # Prevent malicious PHP uploads from running
        @uploads {
          path_regexp path /uploads\/(.*)\.php
        rewrite @uploads /

        @wp-admin {
          path  not ^\/wp-admin/*
        rewrite @wp-admin {path}/index.php?{query}

That looks good!

One note, your log directive isn’t exactly right. I think you’re looking for this:

log {
    output file /var/log/caddy_log.log
1 Like

What is the purpose of this block?

@wp-admin {
  path not ^\/wp-admin/*
rewrite @wp-admin {path}/index.php?{query}

A working WordPress Caddyfile would be a good thing to add to our wiki.

1 Like

I had a question from last month. Caddy 2 wordpress config

I strongly recommend you to run it on unix sockets with

php_fastcgi unix//run/php/php7.x-fpm.sock
( where x is your php-fpm version.)

Then change your php-fpm www.conf with

     listen-owner: caddy 
     listen-group: caddy
1 Like

As far as I am aware, it is completely unnecessary.

I don’t believe any officially published examples for web server configuration (e.g. htaccess – WordPress.org Forums) require a rewrite in this manner.

I have personally only ever seen this pattern in Caddyfile configurations, and nobody yet has been able to explain to me why. So I must assume this is a case of citogenesis. I have never used this pattern in my own WordPress configurations, and they run fine.

In v2, this entire rewrite is unnecessary as the php_fastcgi directive handles rewriting to /index.php by default.


let’s say inside the wp-content folder of WordPress folder ( example /usr/local/www/wordpress/wp-content ) ,
there’s a malicious plug-in or bad script , call bad.php

with the rewrite @wp-admin , it only allow to run .php script in /wordpress/wp-admin and no where else. Just an extra precaution.

If a malicious plugin or bad script exists on disk and is executing, it’s already too late. Malicious PHP running under the guise of an authorized plugin does not necessarily gain more privileges by calling other malicious PHP, so preventing this does not provide any sort of guarantee of damage mitigation.

So on a conceptual level I consider this strategy to be fairly bunk. On top of that, the actual rewrite does not serve the purpose you describe, anyway. If the idea is to limit PHP from executing outside of wp-admin, that’s hopelessly failed. Your rewrite does not prevent someone from uploading to /foo/bar/index.php and calling /foo/bar to run the index file. It does not, on its own, prevent someone from uploading to wp-admin in the first place either.

Additionally, the most overwhelmingly common form of the rewrite I’ve seen in the past has been the following v1 Caddyfile syntax:

rewrite {
  if path not_match ^\/wp-admin
  to {path} {path}/ /index.php?{query}

This is even further than the intent that you suggest. And why is this a regular expression? Further indication of cargo cult behaviour, in my opinion; someone sitting down and thinking through what they’re trying to achieve here, with a real understanding of the functionality available, would be able to do a much better job overall - in particular, a substring match would be optimal here.

In short, it seems very likely to me that many people have taken this solution and worked their way backwards to the problem, convincing themselves it is common and necessary, instead of going about things from the start with the identification of a problem and then incorporating a solution.

This particular problem - the concept of trying to limit damage from malicious PHP - starts and stops at the upload process. WordPress already denies uploading arbitrary PHP through its own uploader. Don’t install untrustworthy plugins, or you’re already pwned the moment they run - and they can hook arbitrary code into any page request.

If you truly want to enact this as a security measure, there’s a slightly more focused bit of config that also floats its way around:

rewrite {
	r /uploads\/(.*)\.php
	to /

Both this and the above are even on our community-maintained v1 configuration examples list! But this one, at least, is much clearer about its purpose. It’s still pretty suboptimal, though. I’d rather do something like this:

rewrite /wp-content/uploads {
  ext .php
  to /

Or in v2 maybe something like this:

route /wp-content/uploads {
  rewrite *.php /

But again I strictly would not put these in my WordPress Caddyfiles because I know that in the scenario where they may be useful my site is already compromised and they will… not be useful.

Edit: And why only Caddy? Not necessary for functionality; not proven to be more secure - in concept OR in practice; not commonly found in comparable config for any other web server. It’s just… been hanging around since the early Caddy v1 days.


In many level, I do agree with you that in paper, having the rewrite won’t help much if malicious script already in the system. But there is only if everything expected to work as it is.

Scenario: let’s assume a noob like me are building a webserver the very first time. Unlike Nginx where it show “Welcome to Nginx” without any index.html at www folder, Caddy just show nothing. And after numerous of trying and trying, and with some miracle, it finally work. The page loaded. Without screw things up again, I leave it as it is without realize I have left some very private .jpg file or any other file in my www folder or wp-admin folder. Without the rewrite, this will give a small possibility for anyone to try their luck on getting something from the server, and someone might hit a jackpot and found www.mydomain.com/wp-admin/mynakedpic.jpg , or www.mydomain.com/wp-content/blxwjob.jpg

The rewrite, which I think what it serve, is to eliminate this to happen.

I only started really playing with Caddy as little as 2 weeks ago. Before that I am just happy that Caddy v1 is alot quicker and shorter to get things going compare to Nginx , and it HTTPS everything ( yaahoo! ) I do apologize if my rewrite is wrong, because with very little sample that I can make sense out of it, and know very little about both v1 and v2 since alot of command or expression has change, it was not easy for me to finally get Caddy v2 running. And that does not include adding cloudflare module , only found out I need xcaddy, then realize I need Go before anything could work.

But your advice is certainly welcome. I will follow what you said, test it out, before I add it to V2 wiki. With that in mind, do help me out on the nextcloud caddyfile v1 to v2. Is a very long and complicated Caddyfile

I just figure out the “quote whole post” function, ( I thought is priviate message , didn’t click on it ) and I just learn to use ‘’’ too

I believe the WordPress v1 Caddyfile comes from Nginx config file which recommend by WordPress. ( Nginx – WordPress.org Forums ) Just never completed i guess.

But for nextcloud v1 caddy file, I am sure is from nextcloud nginx config file
Nextcloud is very pride about their security. I see some similarity between the rewrite for both WordPress and Nextcloud , so needless to say, it should serve some purpose.

I will just show WordPress Nginx Config file and leave Nextcloud stuff for other post
This is from Nginx where WordPress pointed it to

# Upstream to abstract backend connection(s) for php
upstream php {
        server unix:/tmp/php-cgi.socket;

server {
        ## Your website name goes here.
        server_name domain.tld;
        ## Your only path reference.
        root /var/www/wordpress;
        ## This should be in your http block and if it is, it's not needed here.
        index index.php;

        location = /favicon.ico {
                log_not_found off;
                access_log off;

        location = /robots.txt {
                allow all;
                log_not_found off;
                access_log off;

        location / {
                # This is cool because no php is touched for static content.
                # include the "?$args" part so non-default permalinks doesn't break when using query string
                try_files $uri $uri/ /index.php?$args;

        location ~ \.php$ {
                #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
                include fastcgi_params;
                fastcgi_intercept_errors on;
                fastcgi_pass php;
                #The following parameter can be also included in fastcgi_params file
                fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;

        location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
                expires max;
                log_not_found off;
  1. WordPress won’t run a php file out of wp-content anyway. That comes out of the box.
  2. It runs .php out of the root, so apparently this block didn’t work anyway… unless I’m misunderstanding it.

@gyfer, from the nginx configuration you linked, in the global restrictions file, we see this pattern (the behaviour of which does not appear to be present at all in recommended Apache configuration):

location ~* /(?:uploads|files)/.*\.php$ {
    deny all;

This would be equivalent to the following configuration in Caddy v2:

@uploads {
  path_regexp /(uploads|files)/.*\.php$
respond @uploads 403

Note that it also covers the files directory, and that it does not rewrite, it denies access outright. This is the only part of this that I’d consider even slightly functional, and I still don’t think it’s necessary. Not unless you’ve specifically identified some kind of practical exploit this prevents. But, again, I don’t think you will - because if malicious code is already being executed by the web server, it’s too late for you to prevent more bad stuff happening.

In your linked Nextcloud documentation, I can see no nginx configuration that denies access to PHP files within certain directories.

This line of thinking is dangerous - it’s exactly the kind of thought that sees this ineffectual config parroted across the internet without actual understanding of what it’s doing and why. Nextcloud and WordPress are extremely different programs with very different requirements. Saying “all these people are doing similar stuff so it must be important” is exactly the kind of cargo cult behaviour I describe above.

About the only thing that is similar - which is almost universally true of PHP applications, to the point where Caddy v2 now does this automatically with the php_fastcgi directive (!) - is rewriting to PHP index files if the requested file doesn’t exist. The following is actually taken from the expanded form of php_fastcgi:

	# 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 {http.matchers.file.relative}

php_fastcgi (Caddyfile directive) — Caddy Documentation

This means that rewriting, to use the v1 example, to {path} {path}/ /index.php or similar is completely unnecessary, because it’s already being done by Caddy v2. To do so manually - and then for some arbitrary reason, exclude this rewrite for just the wp-admin directory - is just about completely nonsensical at this stage.

This universal rewrite to index is shown in your quoted config, this section here:

        location / {
                # This is cool because no php is touched for static content.
                # include the "?$args" part so non-default permalinks doesn't break when using query string
                try_files $uri $uri/ /index.php?$args;

They’re the same thing. And neither of them bother excluding wp-admin from the rewrite. It just doesn’t make sense to.

        location ~ ^\/nextcloud\/(?:build|tests|config|lib|3rdparty|templates|data)\/ {
            deny all;
        location ~ ^\/nextcloud\/(?:\.|autotest|occ|issue|indie|db_|console) {
            deny all;
  1. if url path is *.com/nextcloud/xxx/ ( * is a wildcard )
    where xxx , can consist of : build, tests, config, lib, 3rdparty, templates, or data
    deny all
  2. if url path is .com/nextcloud/**/xxx ( * is a wildcard)
    where xxx , can consist of autotest, occ, issue, indie, db_|console

for number 2 example, it can be /nextcloud/anything/autotest.jpg
will be deny , because autotest.jpg fit the scenario.

location ~ ^\/nextcloud\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+)\.php(?:$|\/) {

This one will only allow /nextcloud/xxx.php ,
where index.php , remote.php, public.php, cron.php, core/ajax/update.php , status.php , /12.php updaterV.php , will only allow run

location ~ ^\/nextcloud\/.+[^\/]\.(?:css|js|woff2?|svg|gif|map)$ {

mean /nextcloud/anyfolder/*.xxx , where xxx can be css, js, woff2, svg, gif, map
is allow to service these file.

Let me give you an scenario
To be secure, webserver should only give access for anything as mentioned above, which is accurate. Being a lazy man , when I trying to move caddy executable fun from one server to another server , by placing caddy , into any forlder , I was not able to fetch the file caddy over.

so instead, I rename caddy into caddy.jpg , and TA-DA…
fetch https://mydoman.com/nextcloud/caddy.jpg work !
I just need to rename caddy.svg to caddy, and continue my work.

Unless you are saying current Caddy V2 out of the box will not let you fetch the file by placing caddy file at www , or www/nextcloud/morememe folder, then the rewrite is necessary.

Indeed, as you say. This is not denying PHP execution, this is just outright stopping those from being accessed at all. Doesn’t match the Caddyfile WordPress “security” examples we’ve seen in strategy OR execution, and is very specific to Nextcloud in particular.

This isn’t denying execution, this is whitelisting PHP execution from certain folders. Again, a totally different approach. Incidentally, this is incredibly easy to do in Caddy v2 as well!

@whitelistPHP {
  path_regexp ^\/nextcloud\/(index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+)\.php$
php_fastcgi @whitelistPHP

Whitelisting access in general is very different from only doing fallback index rewrites when the directory is not wp-admin and doing an explicit index-only rewrite from PHP requests to an uploads folder. If you were incredibly security-conscious, I’d grant you that explicitly whitelisting exactly which PHP files you know are safe and authorize for execution would be a valid method of proceeding, if meticulous in the extreme.

But there’s another method for whitelisting PHP that’s allowed to execute on your server that’s also incredibly effective: don’t allow a PHP file to be written to disk in the first place if you don’t want it executed. WordPress does this by default (denies arbitrary PHP file uploads), so out of the box it is difficult (impossible?) to exploit in this manner. Again, if YOU override WordPress by adding plugin PHP code that then pwns you, it’s already too late, you’ve already bypassed your own whitelist.

1 Like

To illustrate the “danger” , and with little love-to-experiment in heart, here is a scenario. Perhaps somebody like Matt is secretly working on a version 3 of Caddy, and no one suppose to know. What is known is the file name is call


Under a server serving nextcloud, it has rewrite to restrict no *.svg can be read from /nextcloud/lib/ folder (this one is server Caddy v1)


While other server , running Caddy v2 without the rewrite, has same file in /wordpress/wp-admin/


Matt’s secret file caddy-circle-lock.svg will be stolen by someone else, unlike the one with rewrite.

As a control measure to show both are really working in production environment:


list of files:

root@nextcloud:/usr/local/www/nextcloud # ls -al darth*
-rwxr--r--  1 www  www  117607 May  7 10:08 darthvadar.png

root@1cloud:/usr/local/www/nextcloud # ls -al lib/caddy*
-rw-r--r--  1 root  www  22561 May  7 10:14 lib/caddy-circle-lock.svg

root@nextcloud:/usr/local/www/nextcloud #
root@wp2:/rewrite-no # ls -al dar*
-rwxr-x---  1 root  wheel  117607 May  7 09:04 darthvadar.png

root@wp2:/rewrite-no # ls -al wp-admin/caddy*
-rwxr-x---  1 root  www  22561 May  7 10:12 wp-admin/caddy-circle-lock.svg

root@wp2:/rewrite-no #

Hopefully this is enough to illustrate the important of rewrite as part of security measurement.

Aye, I see your point.

But, again I have to stress this - the “security” features we’ve seen tacked on to WordPress Caddyfiles do not address your point, as it stands - either in strategy or execution.

Further, I really should state that the expected behaviour of a web server administrator (Caddy’s intended user) is… well… NOT to put sensitive files in a public web root! Just don’t do it. The most basic, fundamental concept of a web server is to serve files from a web root!

It’s like - yes, you could maintain a full list of individual files that your web server is allowed to serve, if you want. Then you could put those files anywhere, amongst any other number of possibly sensitive files. But why? There’s already a method to tell your webserver what files should be allowed to be served to a request: by putting them in your public website’s web root directory. This is already, conceptually speaking, a whitelist.

Don’t put sensitive files in a web root! :joy:

None of the WP Caddyfile security examples will truly prevent you from getting your server pwned or sensitive files stolen if you are in the habit of allowing sensitive files or arbitrary executable code inside your web root.


Agree, but not everyone is a IT expert
As a Network Engineer, Data Scientist, or both ( aka NErDS :rofl:) , no safety measure is “too safe”

If you’re capable of installing, configuring, and operating a web server, you meet the standard of web server administrator.

It’s kind of like turning off one specific corner of your barbeque. You can’t cook anything there - but look, if I accidentally lean on this spot, it won’t burn me! Well, yes, but maybe light the whole barbeque up so you can cook more food at once and actually find a rail or table to lean on instead? That’s the point of a barbeque. Turning off one corner in case you lean on it won’t stop you from being burned if you lean on the opposite corner, either.

Don’t have to be a pro chef to get that one, just gotta know how to cook some snags. :hotdog:

1 Like

I am running FreeNAS, and it does come with reconfigured Jail ready extra like Nextcloud, but is running version 17, and with alot of settings yet to be done to make it secure.

With some luck, I found this GitHub, follow most of it steps, and get the NextCloud 18 running until now. This is also how I know about Caddy.

(Dan if you are here, THANK YOU SO MUCH!)

My first OS is DOS, and first programming is C.
It took quite a while to know DOS : > dir/s myfile.txt , is > find / -name myfile.txt in FreeNAS, and that is only valid for FreeBSD Distribution. CentOS, RedHat, Fedora and whatever is out there is different.

It took me quite awhile to know below are identical but NOT the same as in platform.
sudo pkg install caddy
sudo apt-get install caddy
sudo docker run caddy

If is already not easy for me, imagine any mediocre person try to make it run, and not knowing the danger or risk.

In a open source community, programming and coding should be something fun instead of watching out predators. And Caddy is an awesome Webserver design for everyone, not just for admin. :wink:

1 Like