Help to migrate Caddyfile V1 to V2 for Nextcloud

1. My Caddy version (caddy version): 2.0 RC3

2. How I run Caddy:

I have been using Caddy V1 to run Nextcloud perfectly and now I would like to use Caddy V2.0 RC3 with Nextcloud

a. System environment:

Freenas 11.3 (FreeBSD 11.3 based O.S)

d. My complete Caddyfile or JSON config:

For Caddy V1 there is the following Caddyfile example that works: examples/Caddyfile at master · caddyserver/examples · GitHub

my-nextcloud-site.com {

root   /var/www/nextcloud
log    /var/log/nextcloud_access.log
errors /var/log/nextcloud_errors.log

fastcgi / 127.0.0.1:9000 php {
	env PATH /bin
	env modHeadersAvailable true
	env front_controller_active true
	connect_timeout 60s
	read_timeout 3600s
	send_timeout 300s
}

header / {
	Strict-Transport-Security		"max-age=15768000;"
	X-Content-Type-Options			"nosniff"
	X-XSS-Protection			"1; mode=block"
	X-Robots-Tag				"none"
	X-Download-Options			"noopen"
	X-Permitted-Cross-Domain-Policies	"none"
	Referrer-Policy				"no-referrer"
}

header /core/fonts {
	Cache-Control				"max-age=604800"
}

# checks for images
rewrite {
	ext .png .html .ttf .ico .jpg .jpeg .css .js .woff .woff2 .svg .gif .map
	r ^/index.php/.*$
	to /{1} /index.php?{query}
}

rewrite {
            r ^/\.well-known/host-meta$
            to /public.php?service=host-meta&{query}
    }
rewrite {
            r ^/\.well-known/host-meta\.json$
            to /public.php?service=host-meta-json&{query}
    }
rewrite {
            r ^/\.well-known/webfinger$
            to /public.php?service=webfinger&{query}
    }

rewrite {
	r ^/index.php/.*$
	to /index.php?{query}
}

rewrite / {
	if {path} not_starts_with /remote.php
	if {path} not_starts_with /public.php
	ext .png .html .ttf .ico .jpg .jpeg .css .js .woff .woff2 .svg .gif .map .html .ttf 
	r ^/(.*)$
	to /{1} /index.php{uri}
}

rewrite / {
	if {path} not /core/img/favicon.ico
	if {path} not /core/img/manifest.json
	if {path} not_starts_with /remote.php
	if {path} not_starts_with /public.php
	if {path} not_starts_with /cron.php
	if {path} not_starts_with /core/ajax/update.php
	if {path} not_starts_with /status.php
	if {path} not_starts_with /ocs/v1.php
	if {path} not_starts_with /ocs/v2.php
	if {path} not /robots.txt
	if {path} not_starts_with /updater/
	if {path} not_starts_with /ocs-provider/
	if {path} not_starts_with /ocm-provider/ 
	if {path} not_starts_with /.well-known/
	to /index.php{uri}
}

# client support (e.g. os x calendar / contacts)
redir /.well-known/carddav /remote.php/carddav 301
redir /.well-known/caldav /remote.php/caldav 301

# remove trailing / as it causes errors with php-fpm
rewrite {
	r ^/remote.php/(webdav|caldav|carddav|dav)(\/?)(\/?)$
	to /remote.php/{1}
}

rewrite {
	r ^/remote.php/(webdav|caldav|carddav|dav)/(.+?)(\/?)(\/?)$
	to /remote.php/{1}/{2}
}

rewrite {
	r ^/public.php/(dav|webdav|caldav|carddav)(\/?)(\/?)$
	to /public.php/{1}
}

rewrite {
	r ^/public.php/(dav|webdav|caldav|carddav)/(.+)(\/?)(\/?)$
	to /public.php/{1}/{2}
}

# .htaccess / data / config / ... shouldn't be accessible from outside
status 404 {
	/.htaccess
	/data
	/config
	/db_structure
	/.xml
	/README
	/3rdparty
	/lib
	/templates
	/occ
	/console.php
}

}

3. The problem I’m having:

I am trying to migrate the Caddyfile V1 to Caddyfile V2 but I do not understand how to migrate the following rewrite blocks :

# checks for images
rewrite {
	ext .png .html .ttf .ico .jpg .jpeg .css .js .woff .woff2 .svg .gif .map
	r ^/index.php/.*$
	to /{1} /index.php?{query}
}

rewrite {
            r ^/\.well-known/host-meta$
            to /public.php?service=host-meta&{query}
    }
rewrite {
            r ^/\.well-known/host-meta\.json$
            to /public.php?service=host-meta-json&{query}
    }
rewrite {
            r ^/\.well-known/webfinger$
            to /public.php?service=webfinger&{query}
    }

rewrite {
	r ^/index.php/.*$
	to /index.php?{query}
}

rewrite / {
	if {path} not_starts_with /remote.php
	if {path} not_starts_with /public.php
	ext .png .html .ttf .ico .jpg .jpeg .css .js .woff .woff2 .svg .gif .map .html .ttf 
	r ^/(.*)$
	to /{1} /index.php{uri}
}

rewrite / {
	if {path} not /core/img/favicon.ico
	if {path} not /core/img/manifest.json
	if {path} not_starts_with /remote.php
	if {path} not_starts_with /public.php
	if {path} not_starts_with /cron.php
	if {path} not_starts_with /core/ajax/update.php
	if {path} not_starts_with /status.php
	if {path} not_starts_with /ocs/v1.php
	if {path} not_starts_with /ocs/v2.php
	if {path} not /robots.txt
	if {path} not_starts_with /updater/
	if {path} not_starts_with /ocs-provider/
	if {path} not_starts_with /ocm-provider/ 
	if {path} not_starts_with /.well-known/
	to /index.php{uri}
}

# client support (e.g. os x calendar / contacts)
redir /.well-known/carddav /remote.php/carddav 301
redir /.well-known/caldav /remote.php/caldav 301

# remove trailing / as it causes errors with php-fpm
rewrite {
	r ^/remote.php/(webdav|caldav|carddav|dav)(\/?)(\/?)$
	to /remote.php/{1}
}

rewrite {
	r ^/remote.php/(webdav|caldav|carddav|dav)/(.+?)(\/?)(\/?)$
	to /remote.php/{1}/{2}
}

rewrite {
	r ^/public.php/(dav|webdav|caldav|carddav)(\/?)(\/?)$
	to /public.php/{1}
}

rewrite {
	r ^/public.php/(dav|webdav|caldav|carddav)/(.+)(\/?)(\/?)$
	to /public.php/{1}/{2}
}

4. Error messages and/or full log output:

Nextcloud will not work as intended without a proper Caddyfile V2

5. What I already tried:

I started converting the Caddyfile without these rewrite blocks but it is not enough as NextCloud will not work as intended.

my-nextcloud-site.com {

        root    * /usr/local/www/nextcloud
        log {
                output file     /var/log/nextcloud2_access.log
                format single_field common_log
        }

        tls {
                ca https://acme-v02.api.letsencrypt.org/directory
        }

        php_fastcgi 127.0.0.1:9000

        header {
                Strict-Transport-Security               "max-age=15768000;"
                X-Content-Type-Options                  "nosniff"
                X-XSS-Protection                        "1; mode=block"
                X-Robots-Tag                            "none"
                X-Download-Options                      "noopen"
                X-Permitted-Cross-Domain-Policies       "none"
                X-Frame-Options "SAMEORIGIN"
                Referrer-Policy                         "no-referrer"
        }

        header /core/fonts {
                Cache-Control                           "max-age=604800"
        }
        # .htaccess / data / config / ... shouldn't be accessible from outside
        respond @forbidden 404

        @forbidden {
                path    /.htaccess
                path    /data/*
                path    /config/*
                path    /db_structure
                path    /.xml
                path    /README
                path    /3rdparty/*
                path    /lib/*
                path    /templates/*
                path    /occ
                path    /console.php
   }

}

6. Links to relevant resources:

1 Like

Please elaborate, what doesn’t work? Which URLs are you trying? Are you seeing any errors? What are your logs?

Thank you for your reply.

The main landing page of Nextcloud works, but the related functions behind such as the file browser do not work hence I get blank content.

For a start, I found that I get 405 errors codes in my log file such as :
“SEARCH /index.php/apps/files/ HTTP/2.0” 405 0
“PROPFIND /remote.php/dav/files/jacques/ HTTP/2.0” 405 0

I think it’s because I miss this rewrite block in my Caddyfile which I don’t know how to convert to V2 :

    rewrite / {
            if {path} not /core/img/favicon.ico
            if {path} not /core/img/manifest.json
            if {path} not_starts_with /remote.php
            if {path} not_starts_with /public.php
            if {path} not_starts_with /cron.php
            if {path} not_starts_with /core/ajax/update.php
            if {path} not_starts_with /status.php
            if {path} not_starts_with /ocs/v1.php
            if {path} not_starts_with /ocs/v2.php
            if {path} not /robots.txt
            if {path} not_starts_with /updater/
            if {path} not_starts_with /ocs-provider/
            if {path} not_starts_with /ocm-provider/
            if {path} not_starts_with /.well-known/
            to /index.php{uri}
    }

Sounds like you’re having the same problems as this:

I don’t think that rewrite block is necessary in v2. All PHP files will be served by fastcgi if they exist on disk. The only URLs that need or may need special rewrites from those are /.well-known/, /updater/, /ocs-provider/ and /ocm-provider/

This is a long-shot but if you turn off logging (remove those lines), does WebDav start working? We had a potentially related issue regarding logging messing with response headers. I’m not certain that’s the problem, but it’s a hunch.

1 Like

@Dougy I second this. I agree it’s a long shot, since I thought the issue was originally linked only to HTTP Trailers, but we recently found that is not the case so if it fixes your case too, I’d be willing to cherry-pick the fix for v2 release. But if you can test it today or tomorrow that would be superb. :ok_hand:

(“test it” == either removing the log directive OR building with the h2c branch, but removing the log is probably easier.)

Hi guys, I tried removing the log lines but it still doesn’t work. Thank your for your help though. I will look into your other advice tomorrow.

Hi there,

I tried everything, I even reduced my Caddyfile to bare minimum with only a root, a file_server and a php_cgi directive but I keep getting these 405 errors with webdav requests.

I also tried to enforce HTTP 1.1 with alpn http/1.1 to see if this was a compatibility problem with HTTP2…

Here is what I get in debug mode :

2020/04/22 19:46:11.911 DEBUG http.handlers.reverse_proxy upstream roundtrip {“upstream”: “127.0.0.1:9000”, “request”: {“method”: “PROPFIND”, “uri”: “index.php”, “proto”: “HTTP/2.0”, “remote_addr”: “192.168.1.115:50130”, “host”: “mydomain.com, “headers”: {“Depth”: [“1”], “User-Agent”: [“Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15”], “Requesttoken”: [“0kL+3s2HTeCYoiULJv0nu0XNeHfMjKCmje5jsfnBzj0=:gzfKkaj+Ca7gwFFdFaV13CeCICSYwfrkpoIz+KOSglI=”], “Content-Type”: [“application/xml; charset=utf-8”], “Accept”: [”/"], “X-Requested-With”: [“XMLHttpRequest”], “Origin”: [“https://mydomain.com”], “X-Forwarded-For”: [“192.168.1.115”], “Accept-Language”: [“fr-fr”], “Accept-Encoding”: [“br, gzip, deflate”], “Content-Length”: [“621”], “Cookie”: [“nc_session_id=s85tpk5pu40plndcqi9698efjn; nc_token=d55WEe3oKywpV1tBTSZkb5bercFtPLDy; nc_username=jacques; ocih7olqwfvd=s85tpk5pu40plndcqi9698efjn; __Host-nc_sameSiteCookielax=true; __Host-nc_sameSiteCookiestrict=true; oc_sessionPassphrase=YEKymFs5skB0HYwqt17JIj3yqTrGoCemtdOT0LJtPd3ICZXz2RDKLaNXR9i%2BxNwC%2FaQRcAclD7Ghw%2BP4FB0W4Lums9aSgaiSkY7gGWmrhW9OIs7V0hFKaAzYTERrEEc5”]}, “tls”: {“resumed”: false, “version”: 772, “ciphersuite”: 4865, “proto”: “h2”, “proto_mutual”: true, “server_name”: “mydomain.com”}}, “headers”: {“Status”: [“405 Method Not Allowed”], “Referrer-Policy”: [“no-referrer”], “Cache-Control”: [“no-store, no-cache, must-revalidate”], “Pragma”: [“no-cache”], “X-Frame-Options”: [“SAMEORIGIN”], “X-Xss-Protection”: [“1; mode=block”], “Content-Type”: [“text/html; charset=UTF-8”], “X-Powered-By”: [“PHP/7.4.4”], “Expires”: [“Thu, 19 Nov 1981 08:52:00 GMT”], “Content-Security-Policy”: [“default-src ‘self’; script-src ‘self’ ‘nonce-REhvMmI2bkJBT1VXTmpFVDA1ZnIzVGNFTW5QRmNBczdYY1VUeTRtcy9Haz06WFE4Q0lNeTRSS3R1VkVWRjRNKzV1bFZMYWlDUlBWRjVkcWxEZ3RQL3NBWT0=’; style-src ‘self’ ‘unsafe-inline’; frame-src *; img-src * data: blob:; font-src ‘self’ data:; media-src *; connect-src *; object-src ‘none’; base-uri ‘self’;”], “X-Download-Options”: [“noopen”], “X-Content-Type-Options”: [“nosniff”], “X-Permitted-Cross-Domain-Policies”: [“none”], “X-Robots-Tag”: [“none”]}, “duration”: 0.085495232, “status”: 405}
2020/04/22 19:46:11.911 ERROR http.log.access.log0 handled request {“request”: {“method”: “PROPFIND”, “uri”: “/remote.php/dav/files/jacques/”, “proto”: “HTTP/2.0”, “remote_addr”: “192.168.1.115:50130”, “host”: “mydomain.com”, “headers”: {“Accept-Language”: [“fr-fr”], “Accept-Encoding”: [“br, gzip, deflate”], “Content-Length”: [“621”], “Cookie”: [“nc_session_id=s85tpk5pu40plndcqi9698efjn; nc_token=d55WEe3oKywpV1tBTSZkb5bercFtPLDy; nc_username=jacques; ocih7olqwfvd=s85tpk5pu40plndcqi9698efjn; __Host-nc_sameSiteCookielax=true; __Host-nc_sameSiteCookiestrict=true; oc_sessionPassphrase=YEKymFs5skB0HYwqt17JIj3yqTrGoCemtdOT0LJtPd3ICZXz2RDKLaNXR9i%2BxNwC%2FaQRcAclD7Ghw%2BP4FB0W4Lums9aSgaiSkY7gGWmrhW9OIs7V0hFKaAzYTERrEEc5”], “Content-Type”: [“application/xml; charset=utf-8”], “Accept”: ["/"], “X-Requested-With”: [“XMLHttpRequest”], “Origin”: [“https://mydomain.com”], “Depth”: [“1”], “User-Agent”: [“Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15”], “Requesttoken”: [“0kL+3s2HTeCYoiULJv0nu0XNeHfMjKCmje5jsfnBzj0=:gzfKkaj+Ca7gwFFdFaV13CeCICSYwfrkpoIz+KOSglI=”]}, “tls”: {“resumed”: false, “version”: 772, “ciphersuite”: 4865, “proto”: “h2”, “proto_mutual”: true, “server_name”: “mydomain.com”}}, “common_log”: “192.168.1.115 - - [22/Apr/2020:21:46:11 +0200] “PROPFIND /remote.php/dav/files/jacques/ HTTP/2.0” 405 0”, “latency”: 0.085807279, “size”: 0, “status”: 405, “resp_headers”: {“Content-Type”: [“text/html; charset=UTF-8”], “X-Content-Type-Options”: [“nosniff”, “nosniff”], “Cache-Control”: [“no-store, no-cache, must-revalidate”], “Status”: [“405 Method Not Allowed”], “Content-Security-Policy”: [“default-src ‘self’; script-src ‘self’ ‘nonce-REhvMmI2bkJBT1VXTmpFVDA1ZnIzVGNFTW5QRmNBczdYY1VUeTRtcy9Haz06WFE4Q0lNeTRSS3R1VkVWRjRNKzV1bFZMYWlDUlBWRjVkcWxEZ3RQL3NBWT0=’; style-src ‘self’ ‘unsafe-inline’; frame-src *; img-src * data: blob:; font-src ‘self’ data:; media-src *; connect-src *; object-src ‘none’; base-uri ‘self’;”], “X-Frame-Options”: [“SAMEORIGIN”, “SAMEORIGIN”], “X-Permitted-Cross-Domain-Policies”: [“none”, “none”], “Strict-Transport-Security”: [“max-age=15768000;”], “X-Download-Options”: [“noopen”, “noopen”], “Pragma”: [“no-cache”], “X-Powered-By”: [“PHP/7.4.4”], “Server”: [“Caddy”], “X-Robots-Tag”: [“none”, “none”], “Expires”: [“Thu, 19 Nov 1981 08:52:00 GMT”], “X-Xss-Protection”: [“1; mode=block”, “1; mode=block”], “Referrer-Policy”: [“no-referrer”, “no-referrer”]}}
1 Like

Ah, I think this is the clue. The rewrite is making your request go through index.php rather than remote.php.

Let me think of what the right solution is…

Nice, I’m diggin’ the table view.

Hmm, well it’s beyond me what is wrong then. Nextcloud is clearly returning a 405 “Method not allowed” and I don’t know enough about Nextcloud to know why. Try asking Nextcloud? The logs should have plenty of information to help people familiar with it what is wrong. Will be happy to make any fixes in Caddy 2 if something is wrong with it.

Could you try adding this to your Caddyfile? I’m not certain the syntax is correct, but I think the idea is sound.

@try_files {
    path_regexp phpfile ^(.*\.php)
    file {
        try_files {path} {http.regexp.phpfile.1} {path}/index.php index.php
    }
}
rewrite @try_files {http.matchers.file.relative}

Edit: Try {http.regexp.phpfile.0} if it doesn’t work at first, I’m not sure if that regexp match is 0 or 1

Hmm I’m not sure we can guarantee that the regexp matcher runs before the file matcher. (Maps aren’t ordered.)

Thought so… is there a way we can guarantee that?

Probably use a route or handle directive so that the outer matcher is guaranteed to run first.

Dang, I don’t love that… we’d need to wrap the entire config in a route to do it… :thinking: there must be a better way

Thank you for your help.

To understand better what was the needed rewrite request, I switched back to Caddy v1 and modified this rewrite block from Caddyfile :

rewrite / {
        if {path} not /core/img/favicon.ico
        if {path} not /core/img/manifest.json
        if {path} not_starts_with /remote.php
        if {path} not_starts_with /public.php
        if {path} not_starts_with /cron.php
        if {path} not_starts_with /core/ajax/update.php
        if {path} not_starts_with /status.php
        if {path} not_starts_with /ocs/v1.php
        if {path} not_starts_with /ocs/v2.php
        if {path} not /robots.txt
        if {path} not_starts_with /updater/
        if {path} not_starts_with /ocs-provider/
        if {path} not_starts_with /ocm-provider/
        if {path} not_starts_with /.well-known/
        to /index.php{uri}
}

I replaced it with the one below

rewrite / {
        to /index.php{uri}
}

Now I get the exact same behaviour as Caddy V2 :
"PROPFIND /remote.php/dav/files/jacques/ HTTP/1.1" 405 0

Thus, I just need to understand how to prevent Caddyfile v2 to rewrite a request such as /remote.php/* to be redirected to index.php

Could it be due to the new V2 php_fastcgi directive which redirects everything to index.php ?

Yep that’s exactly why @Dougy, I’m just trying to think of a generalized solution so we can solve it without relying on specifically dealing with each path that involves a non-index .php file.

Once we figure that out I’ll probably look into baking it into the php_fastcgi directive so it’s generally available.

The crux of the issue is that we made the assumption that an app would only have one PHP file that would act as a router (index.php typically) but NextCloud has more than one (i.e. remote.php as well)

To clarify further, if the requested URI was something like /remote.php?p=/dav/files/jacques it would work fine because {path} would only be /remote.php because the query is not included, but in this case the actual requested URI is /remote.php/dav/files/jacques/ and Caddy doesn’t detect that the .php part denotes the end of the filename.

Okay I think I’ve thought through a solution – the general idea is to add a split feature to the file matcher, it would look something like this:

file {
    split .php
    try_files {path} {path}/index.php index.php
}

I’ll work on implementing that soon, we’ll see if we can get that into Caddy ASAP, but no promises.

That said, here’s a potential temporary solution for you:

@phpFiles {
    path_regexp phpfile ^/(remote|public|cron|core/ajax/update|status|ocs/v1|ocs/v2)\.php
}
rewrite @phpFiles {http.regexp.phpfile.0}

Essentially this should act as a whitelist for those PHP files that shouldn’t be rewritten to index.php.

1 Like

@francislavoie, your solution is working, thank you !

This is my current Caddyfile V2 :

   mydomain.com {

            root    * /usr/local/www/nextcloud
            file_server
            log {
                    output file     /var/log/nextcloud_access.log
                    format single_field common_log
            }

            tls {
                    ca https://acme-v02.api.letsencrypt.org/directory
            }

            php_fastcgi 127.0.0.1:9000

            header {
                    Strict-Transport-Security               "max-age=15768000;"
                    X-Content-Type-Options                  "nosniff"
                    X-XSS-Protection                        "1; mode=block"
                    X-Robots-Tag                            "none"
                    X-Download-Options                      "noopen"
                    X-Permitted-Cross-Domain-Policies       "none"
                    X-Frame-Options "SAMEORIGIN"
                    Referrer-Policy                         "no-referrer"
            }

            header /core/fonts {
                    Cache-Control                           "max-age=604800"
            }

            @phpFiles {
                    path_regexp phpfile ^/(remote|public|cron|core/ajax/update|status|ocs/v1|ocs/v2)\.php
            }
            rewrite @phpFiles {http.regexp.phpfile.0}

            redir /.well-known/cardav /remote.php/dav 301
            redir /.well-known/caldav /remote.php/dav 301

            # .htaccess / data / config / ... shouldn't be accessible from outside
            @forbidden {
                    path    /.htaccess
                    path    /data/*
                    path    /config/*
                    path    /db_structure
                    path    /.xml
                    path    /README
                    path    /3rdparty/*
                    path    /lib/*
                    path    /templates/*
                    path    /occ
                    path    /console.php
            }

            respond @forbidden 404

    }
3 Likes

@Dougy are you willing to try out an early build to see if this works for you?

Seeing as you’re on FreeBSD you’d probably need to compile it yourself, let me know if you need any pointers for how to do that!

Build instructions are here:

Basically, just comment out or remove the rewrite block I had you add, and hopefully it should work with the new build. The patch changes how php_fastcgi behaves to handle your situation.

1 Like