V2: WordPress Caddyfile

My bad, I thought you were referring to the Caddy log. The PHP-FPM error log just repeats itself over and over:

[06-Apr-2020 18:00:58 UTC] PHP Fatal error:  Maximum execution time of 30 seconds exceeded in C:\Users\Ryan\www\wp\wp-includes\Requests\Transport\cURL.php on line 163
[06-Apr-2020 18:03:59 UTC] PHP Fatal error:  Maximum execution time of 30 seconds exceeded in C:\Users\Ryan\www\wp\wp-includes\Requests\Transport\cURL.php on line 163
[06-Apr-2020 18:04:02 UTC] PHP Fatal error:  Maximum execution time of 30 seconds exceeded in C:\Users\Ryan\www\wp\wp-includes\Requests\Transport\cURL.php on line 163

I have the cURL extension enabled, and cURL installed in Windows.

Okay, then that’s definitely not an issue with Caddy, that means that your cron is attempting to make an HTTP request using curl to somewhere but it’s timing out. Maybe you can add a debug line just before that spot to see what the request address is that your code is trying to hit? Something like error_log($url); I don’t know the WordPress codebase very well.

Edit: I think this is the code: cURL.php in tags/5.4/src/wp-includes/Requests/Transport – WordPress Trac so yes, error_log($url); somewhere before curl_exec() should clarify what’s going on, I hope.

2 Likes

Thank you!
I’ve solved the issue by checking the URLs being fetched via cURL, and the Hummingbird plugin (WordPress) was stuck in a loop calling the URLs over and over.

As you’ve said, this has nothing to do with Caddy v2, but it was somehow working with v1.

2 Likes

Great! Glad you figured it out!

1 Like

Quick update on the issue: WordPress 5.4 seems to not be fully compatible with PHP 7.4 just yet. This seems to definitely be the issue, as downgrading to 7.3.15 solved every single problem.

3 Likes

A post was split to a new topic: Wordpress + secure connections

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.

3 Likes

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
        php_fastcgi 127.0.0.1:9000

        # 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}
        file_server
}

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 – Documentation – WordPress.org) 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.

3 Likes

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.

4 Likes

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 – Documentation – WordPress.org ) Just never completed i guess.

But for nextcloud v1 caddy file, I am sure is from nextcloud nginx config file
https://docs.nextcloud.com/server/18/admin_manual/installation/nginx.html
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 127.0.0.1:9000;
}

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.

2 Likes
        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.