Nextcloud Setup broken after update to 2.10.2

1. The problem I’m having:

After updating caddy from 2.10.0 to 2.10.2 I’ve noticed that my Nextcloud had some issues, namely files not being displayed anymore. After digging deeper into it I’ve noticed a lot of requests to NC failing with status 400 that haven’t failed before. NC handles requests really weirdly and I’m almost sure that the issue is somewhere within my Caddyfile, but I’m not that familiar with Caddy yet to figure out whats wrong. I suspect that this PR changed some behavior which is why I’m seeing this issue. I am aware that I am not supposed to redact the domain names, I have chosen to do so anyway for my own protection. Sorry.

2. Error messages and/or full log output:

With 2.10.0 (working as intended):

2025/11/08 09:05:14.653 DEBUG   http.handlers.file_server       sanitized path join     {"site_root": "/var/www/cloud.<DOMAIN REPLACED>", "fs": "", "request_path": "/remote.php/dav/files/aurora/", "result": "/var/www/cloud.<DOMAIN REPLACED>/remote.php/dav/files/aurora"}
2025/11/08 09:05:14.653 DEBUG   http.handlers.rewrite   rewrote request {"request": {"remote_ip": "<IP REPLACED>", "remote_port": "52548", "client_ip": "<IP REPLACED>", "proto": "HTTP/1.1", "method": "PROPFIND", "host": "cloud.<DOMAIN REPLACED>", "uri": "/remote.php", "headers": {"User-Agent": ["Mozilla/5.0 (Windows) mirall/4.0.1 (build 20251027) (Nextcloud, windows-10.0.26100 ClientArchitecture: x86_64 OsArchitecture: x86_64)"], "Accept": ["*/*"], "Content-Type": ["text/xml; charset=utf-8"], "Content-Length": ["105"], "Cookie": ["REDACTED"], "Connection": ["Keep-Alive"], "Accept-Encoding": ["zstd, br, gzip, deflate"], "Depth": ["0"], "Authorization": ["REDACTED"], "X-Request-Id": ["d4eb3f35-52ce-4581-8fce-799404e908f3"], "Accept-Language": ["de-AT,en,*"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "", "server_name": "cloud.<DOMAIN REPLACED>"}}, "method": "PROPFIND", "uri": "/remote.php"}
2025/11/08 09:05:14.653 DEBUG   http.reverse_proxy.transport.fastcgi    roundtrip       {"dial": "/var/run/php-fpm/php-fpm.sock", "env": {"SCRIPT_FILENAME": "/var/www/cloud.<DOMAIN REPLACED>/remote.php", "HTTP_X_FORWARDED_HOST": "cloud.<DOMAIN REPLACED>", "REQUEST_SCHEME": "https", "PATH_TRANSLATED": "/var/www/cloud.<DOMAIN REPLACED>/dav/files/aurora", "SSL_CIPHER": "TLS_AES_128_GCM_SHA256", "front_controller_active": "true", "HTTP_ACCEPT": "*/*", "HTTP_ACCEPT_ENCODING": "zstd, br, gzip, deflate", "REQUEST_URI": "/remote.php/dav/files/aurora/", "HTTPS": "on", "REQUEST_METHOD": "PROPFIND", "SERVER_NAME": "cloud.<DOMAIN REPLACED>", "SERVER_SOFTWARE": "Caddy/v2.10.0", "DOCUMENT_ROOT": "/var/www/cloud.<DOMAIN REPLACED>", "HTTP_ACCEPT_LANGUAGE": "de-AT,en,*", "HTTP_DEPTH": "0", "CONTENT_LENGTH": "105", "PATH_INFO": "/dav/files/aurora", "SERVER_PORT": "443", "modHeadersAvailable": "true", "HTTP_CONTENT_TYPE": "text/xml; charset=utf-8", "HTTP_CONTENT_LENGTH": "105", "HTTP_X_FORWARDED_FOR": "<IP REPLACED>", "REMOTE_PORT": "52548", "SSL_PROTOCOL": "TLSv1.3", "AUTH_TYPE": "", "REMOTE_HOST": "<IP REPLACED>", "SCRIPT_NAME": "/remote.php", "GATEWAY_INTERFACE": "CGI/1.1", "REMOTE_IDENT": "", "QUERY_STRING": "", "REMOTE_ADDR": "<IP REPLACED>", "HTTP_X_FORWARDED_PROTO": "https", "HTTP_VIA": "1.1 Caddy", "HTTP_COOKIE": "", "CONTENT_TYPE": "text/xml; charset=utf-8", "DOCUMENT_URI": "/remote.php", "HTTP_AUTHORIZATION": "", "HTTP_USER_AGENT": "Mozilla/5.0 (Windows) mirall/4.0.1 (build 20251027) (Nextcloud, windows-10.0.26100 ClientArchitecture: x86_64 OsArchitecture: x86_64)", "HTTP_X_REQUEST_ID": "d4eb3f35-52ce-4581-8fce-799404e908f3", "REMOTE_USER": "", "SERVER_PROTOCOL": "HTTP/1.1", "HTTP_HOST": "cloud.<DOMAIN REPLACED>"}, "request": {"remote_ip": "<IP REPLACED>", "remote_port": "52548", "client_ip": "<IP REPLACED>", "proto": "HTTP/1.1", "method": "PROPFIND", "host": "cloud.<DOMAIN REPLACED>", "uri": "/remote.php", "headers": {"X-Forwarded-Host": ["cloud.<DOMAIN REPLACED>"], "Content-Type": ["text/xml; charset=utf-8"], "Content-Length": ["105"], "X-Forwarded-For": ["<IP REPLACED>"], "Accept-Encoding": ["zstd, br, gzip, deflate"], "X-Request-Id": ["d4eb3f35-52ce-4581-8fce-799404e908f3"], "Accept-Language": ["de-AT,en,*"], "X-Forwarded-Proto": ["https"], "Via": ["1.1 Caddy"], "Cookie": ["REDACTED"], "Depth": ["0"], "Authorization": ["REDACTED"], "User-Agent": ["Mozilla/5.0 (Windows) mirall/4.0.1 (build 20251027) (Nextcloud, windows-10.0.26100 ClientArchitecture: x86_64 OsArchitecture: x86_64)"], "Accept": ["*/*"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "", "server_name": "cloud.<DOMAIN REPLACED>"}}}
2025/11/08 09:05:14.684 DEBUG   http.handlers.reverse_proxy     upstream roundtrip      {"upstream": "unix//var/run/php-fpm/php-fpm.sock", "duration": 0.03145498, "request": {"remote_ip": "<IP REPLACED>", "remote_port": "52548", "client_ip": "<IP REPLACED>", "proto": "HTTP/1.1", "method": "PROPFIND", "host": "cloud.<DOMAIN REPLACED>", "uri": "/remote.php", "headers": {"Accept-Language": ["de-AT,en,*"], "X-Forwarded-Proto": ["https"], "Via": ["1.1 Caddy"], "Cookie": ["REDACTED"], "Depth": ["0"], "Authorization": ["REDACTED"], "User-Agent": ["Mozilla/5.0 (Windows) mirall/4.0.1 (build 20251027) (Nextcloud, windows-10.0.26100 ClientArchitecture: x86_64 OsArchitecture: x86_64)"], "Accept": ["*/*"], "X-Forwarded-Host": ["cloud.<DOMAIN REPLACED>"], "Content-Type": ["text/xml; charset=utf-8"], "Content-Length": ["105"], "X-Forwarded-For": ["<IP REPLACED>"], "Accept-Encoding": ["zstd, br, gzip, deflate"], "X-Request-Id": ["d4eb3f35-52ce-4581-8fce-799404e908f3"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "", "server_name": "cloud.<DOMAIN REPLACED>"}}, "headers": {"Status": ["207 Multi-Status"], "Content-Security-Policy": ["default-src 'none';"], "Vary": ["Brief,Prefer"], "X-Request-Id": ["lottBP7cmair46GODy6V"], "X-Debug-Token": ["lottBP7cmair46GODy6V"], "Content-Encoding": ["gzip"], "X-Powered-By": ["PHP/8.4.14"], "Content-Type": ["application/xml; charset=utf-8"], "Dav": ["1, 3, extended-mkcol, access-control, calendarserver-principal-property-search, nc-paginate, nextcloud-checksum-update, nc-calendar-search, nc-enable-birthday-calendar"]}, "status": 207}

With 2.10.2 (broken):

2025/11/08 09:07:26.797 DEBUG   http.handlers.file_server       sanitized path join     {"site_root": "/var/www/cloud.<DOMAIN REPLACED>", "fs": "", "request_path": "/remote.php/dav/files/aurora/", "result": "/var/www/cloud.<DOMAIN REPLACED>/remote.php/dav/files/aurora"}
2025/11/08 09:07:26.797 DEBUG   http.log.error  invalid argument        {"request": {"remote_ip": "<IP REPLACED>", "remote_port": "52702", "client_ip": "<IP REPLACED>", "proto": "HTTP/2.0", "method": "REPORT", "host": "cloud.<DOMAIN REPLACED>", "uri": "/remote.php/dav/files/aurora/", "headers": {"Depth": ["1"], "Origin": ["https://cloud.<DOMAIN REPLACED>"], "Content-Type": ["text/plain;charset=UTF-8"], "Content-Length": ["652"], "Sec-Gpc": ["1"], "Sec-Fetch-Mode": ["cors"], "Cookie": ["REDACTED"], "Accept": ["text/plain,application/xml"], "Requesttoken": ["lqHCCvdnwoXmYd3X5rRrAvZalcY482i4N0JA4dO9/zU=:wJWwa5sPusCPAI6VnoUbdYQX5fNAnEeOfCdvk6XIj3Y="], "Pragma": ["no-cache"], "User-Agent": ["Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0"], "Sec-Fetch-Site": ["same-origin"], "X-Requested-With": ["XMLHttpRequest"], "Dnt": ["1"], "Accept-Language": ["de,en-US;q=0.7,en;q=0.3"], "Te": ["trailers"], "Accept-Encoding": ["gzip, deflate, br, zstd"], "Cache-Control": ["no-cache"], "Sec-Fetch-Dest": ["empty"], "Priority": ["u=4"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "h2", "server_name": "cloud.<DOMAIN REPLACED>"}}, "duration": 0.000134878, "status": 400, "err_id": "nccg40pkc", "err_trace": "fileserver.(*FileServer).ServeHTTP (staticfiles.go:308)"}

This doesn’t seem to be a browser-specific problem, as the NC desktop client and android app also have the same issue. The browser itself doesn’t show anything in the log beside getting 400 returned for that specific request, without any additional content. The request doesn’t reach NC, so that obviously also doesn’t show anything in its logs.

3. Caddy version:

v2.10.0 and v2.10.2

4. How I installed and ran Caddy:

a. System environment:

Arch 6.12.57-1-lts, caddy installed via pacman and run via systemd using the default service file: caddy.service · main · Arch Linux / Packaging / Packages / caddy · GitLab

b. Command:

see a

c. Service/unit/compose file:

n/a

d. My complete Caddy config:

In /etc/caddy/Caddyfile:

{
        debug
        admin "unix//run/caddy/admin.socket"
        email <EMAIL REPLACED>
        log default {
                output file /var/log/caddy/caddy.log {
                        mode 644
                        roll_size 10MiB
                }
                format console
        }
}
(php) {
        php_fastcgi unix//var/run/php-fpm/php-fpm.sock
}
import /etc/caddy/conf.d/*

and in /etc/caddy/conf.d/cloud.<DOMAIN REPLACED>

cloud.<DOMAIN REPLACED> {
        request_body {
                max_size 10G
        }

        encode {
                zstd
                gzip 4

                minimum_length 256

                match {
                        header Content-Type application/atom+xml
                        header Content-Type application/javascript
                        header Content-Type application/json
                        header Content-Type application/ld+json
                        header Content-Type application/manifest+json
                        header Content-Type application/rss+xml
                        header Content-Type application/vnd.geo+json
                        header Content-Type application/vnd.ms-fontobject
                        header Content-Type application/wasm
                        header Content-Type application/x-font-ttf
                        header Content-Type application/x-web-app-manifest+json
                        header Content-Type application/xhtml+xml
                        header Content-Type application/xml
                        header Content-Type font/opentype
                        header Content-Type image/bmp
                        header Content-Type image/svg+xml
                        header Content-Type image/x-icon
                        header Content-Type text/cache-manifest
                        header Content-Type text/css
                        header Content-Type text/plain
                        header Content-Type text/vcard
                        header Content-Type text/vnd.rim.location.xloc
                        header Content-Type text/vtt
                        header Content-Type text/x-component
                        header Content-Type text/x-cross-domain-policy
                }
        }

        header Referrer-Policy no-referrer
        header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;"
        header X-Content-Type-Options nosniff
        header X-Download-Options noopen
        header X-Frame-Options SAMEORIGIN
        header X-Permitted-Cross-Domain-Policies none
        header X-Robots-Tag noindex,nofollow

        root * /var/www/cloud.<DOMAIN REPLACED>
        file_server

        route {
                # Rule borrowed from `.htaccess` to handle Microsoft DAV clients
                @msftdavclient {
                        header User-Agent DavClnt*
                        path /
                }
                redir @msftdavclient /remote.php/webdav/ temporary

                route /robots.txt {
                        skip_log
                        file_server
                }

                # Add exception for `/.well-known` so that clients can still access it
                # despite the existence of the `error @internal 404` rule which would
                # otherwise handle requests for `/.well-known` below
                route /.well-known/* {
                        redir /.well-known/carddav /remote.php/dav/ permanent
                        redir /.well-known/caldav /remote.php/dav/ permanent

                        @well-known-static path \
                                /.well-known/acme-challenge /.well-known/acme-challenge/* \
                                /.well-known/pki-validation /.well-known/pki-validation/*
                        route @well-known-static {
                                try_files {path} {path}/ =404
                                file_server
                        }

                        redir * /index.php{path} permanent
                }

                @internal path \
                        /build /build/* \
                        /tests /tests/* \
                        /config /config/* \
                        /lib /lib/* \
                        /3rdparty /3rdparty/* \
                        /templates /templates/* \
                        /data /data/* \
                        \
                        /.* \
                        /autotest* \
                        /occ* \
                        /issue* \
                        /indie* \
                        /db_* \
                        /console*
                error @internal 404

                @assets {
                        path *.css *.js *.svg *.gif *.png *.jpg *.jpeg *.ico *.wasm *.tflite *.map *.wasm2
                        file {path}  # Only if requested file exists on disk, otherwise /index.php will take care of it
                }
                route @assets {
                        header /*       Cache-Control "max-age=15552000"   # Cache-Control policy borrowed from `.htaccess`
                        header /*.woff2 Cache-Control "max-age=604800"     # Cache-Control policy borrowed from `.htaccess`
                        skip_log                                           # Optional: Don't log access to assets
                        file_server {
                                precompressed gzip
                        }
                }

                # Rule borrowed from `.htaccess`
                redir /remote/* /remote.php{path} permanent

                # Serve found static files, continuing to the PHP default handler below if not found
                try_files {path} {path}/
                @notphpordir not path /*.php /*.php/* / /*/
                file_server @notphpordir {
                        pass_thru
                }

                # Required for legacy support
                #
                # Rewrites all other requests to be prepended by “/index.php” unless they match a known-valid PHP file path.

                @unknownphppath not path \
                        /index.php /index.php/* \
                        /remote.php /remote.php/* \
                        /public.php /public.php/* \
                        /cron.php /cron.php/* \
                        /core/ajax/update.php /core/ajax/update.php/* \
                        /status.php /status.php/* \
                        /ocs/v1.php /ocs/v1.php/* \
                        /ocs/v2.php /ocs/v2.php/* \
                        /updater/*.php /updater/*.php/* \
                        /ocm-provider/*.php /ocm-provider/*.php/* \
                        /ocs-provider/*.php /ocs-provider/*.php/* \
                        /*/richdocumentscode/proxy.php /*/richdocumentscode/proxy.php/*
                rewrite @unknownphppath /index.php{path}

                php_fastcgi unix//var/run/php-fpm/php-fpm.sock {
                        env modHeadersAvailable true         # Avoid sending the security headers twice
                        env front_controller_active true     # Enable pretty urls
                }
        }
}

5. Links to relevant resources:

The Nextcloud caddy config has been pretty much copied from this pull request: Add complete Caddy sample configuration inspired by the NGINX one by ntninja · Pull Request #9199 · nextcloud/documentation · GitHub

1 Like

I have the very same problem. I can confirm that downgrading back to 2.10.0 (easy for me thanks to docker) makes it work again. So something is definitely different for 2.10.2.

EDIT: I also had brief look at the changes between 2.10.0 and 2.10.2. Nothing really stands out to me except the PR that @aurora already identified (and that I definitely saw when reading the post :sweat_smile: ).

1 Like

Can confirm it working with caddy:2.10.0-alpine.

Not well versed in Go but that PR seems to be it.

This part of the Caddyfile is to blame:

@notphpordir not path /*.php /*.php/* / /*/
file_server @notphpordir {
    pass_thru
}

according to fsrv.notFound the pass_thru setting only prevents 404 Not Found Errors.
Invalid Filesystem / Path errors still cause a 400 Bad Request by fs.ErrInvalid.

fs is a GO package for interacting with filesystems that defines ErrInvalid as “invalid argument”.

My assumption is that this is the case because paths such as:

  • /path/to/your/nextcloud/html/index.php/.well-known/webfinger
  • /path/to/your/nextcloud/html/remote.php/dev/files/
  • etc.

are infact invalid as the relevant php-File exists and treating it as a directory is invalid (?).

Will try to adapt my Caddyfile and report back.

2 Likes

It seems ntninja created this Caddyfile on the basis of this nginx config.

This whole issue should be prevented by the not path /*php /*.php/* Caddy Matcher path.

Trying to understand the Caddy documentation on the Matcher gives me the sense that /*.php /*.php/* does not match a path (rewritten by the try_files directive) such as /var/www/html/index.php/.well-known/webfinger as /*.php only matches /index.php not .../index.php. Leaving out the first forward slash (e.g. not path *.php *.php/* / /*/ ) could work instead.

try_files {path} {path}/
@notphpordir not path *.php *.php/* / /*/
file_server @notphpordir {
    pass_thru
}

Seems to work for me on 2.10.2.
Couldn’t find any requests failing with code 400 during a quick test.

I am however still unsure how exactly this even came to be as the reference nginx configuration has no exact counterpart and there exist copies of this caddyfile without this part that seem to be working mostly fine (some assets like the welcome video don’t load).

Hard to gauge without a clear reference which real assets are supposed to be served by this.

1 Like

I’m also on 2.10.2 with no issues. I think we dont need such complicated matchers, etc, as in the original post.

mydomain.com:443 {
	import main mydomain.com
	root * /var/www/domains/mydomain.com/htdocs/nextcloud
	
    header {
		Permissions-Policy interest-cohort=()
		Strict-Transport-Security "max-age=31968000; includeSubDomains; preload"
    }
    @cache {
        path *.js *.css *.csv *.png *.jpg
    }
    header @cache {
	    Cache-Control "max-age=604800, s-maxage=604800, public"
    }
    header -server
    header -via
	# .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

	# Redirect CardDAV and CalDAV endpoints
	redir /.well-known/carddav /remote.php/dav/ 301
	redir /.well-known/caldav /remote.php/dav/ 301
	redir /.well-known/openid-configuration /index.php/apps/oidc/openid-configuration/ 301
	redir /.well-known/webfinger /index.php/.well-known/webfinger 301
	redir /.well-known/nodeinfo /index.php/.well-known/nodeinfo 301

	# Nextcloud push (mobile notifications)
	route /push/* {
		uri strip_prefix /push
		reverse_proxy http://127.0.0.1:7867
	}

	php_fastcgi unix//run/php-fpm/fpm-nextcloud.socket {
		# Needed to remove index.php from URLs. Does not work with rainloop.
		#env front_controller_active true
		trusted_proxies private_ranges
	}
	
	file_server
}
1 Like