Anyone is kind enough to share his Caddy 2 Config?

Trying to use Caddy 2 as a reverse proxy for Nextcloud 19. I have tried almost all configs (Caddyfile) I can find on the internet, but most of them just do not work. The one that works keeps telling me my password is wrong? Thanks.

This Caddyfile works for me. Running Nextcloud 22 on vps with Ubuntu 20.04

cloud.mynextcloud.server { 
	root * /var/www/nextcloud/
	file_server
	php_fastcgi unix//run/php/php7.4-fpm.sock

	redir /.well-known/carddav /remote.php/carddav 301
  	redir /.well-known/caldav /remote.php/caldav 301
        
    # Stop acccess from internet
    respond /config/* "Access denied!" 403
    respond /data/* "Access denied!" 403
    respond /.htacces "Access denied!" 403
    respond /db_structure/* "Access denied!" 403
    respond /.xml "Access denied!" 403
    respond /README "Access denied!" 403

    header {
		Strict-Transport-Security max-age=31536000;
	}
}

@buskjan Thanks for sharing your config. Have you been able to use pretty urls with Caddy?

I can’t seem to get away from myhost.com/index.php/apps/filex and have myhost.com/apps/files like with Apache.

EDIT

Seemed to just have needed to add env front_controller_active true to the php_fastcgi section.

	php_fastcgi unix//run/php-fpm/php-nextcloud.socket {
		env front_controller_active true
	}

EDIT 2: Although it seems to work in mot places, Rainloop does not work with “pretty” URL’s.

When I click the Rainloop icon I get 500 Internal Server Error on https://mydomain.com/apps/rainloop/app/?OwnCloudAuth. Request path is /apps/rainloop/app/?OwnCloudAuth

If I remove the env config the request path is /index.php/apps/rainloop/app/?OwnCloudAuth and it loads OK.

I am wondering also if we need to handle the other rewrite rules specified in the htdocs/.htaccess file?

<IfModule mod_headers.c>
  <IfModule mod_setenvif.c>
    <IfModule mod_fcgid.c>
       SetEnvIfNoCase ^Authorization$ "(.+)" XAUTHORIZATION=$1
       RequestHeader set XAuthorization %{XAUTHORIZATION}e env=XAUTHORIZATION
    </IfModule>
    <IfModule mod_proxy_fcgi.c>
       SetEnvIfNoCase Authorization "(.+)" HTTP_AUTHORIZATION=$1
    </IfModule>
  </IfModule>

  <IfModule mod_env.c>
    # Add security and privacy related headers

    # Avoid doubled headers by unsetting headers in "onsuccess" table,
    # then add headers to "always" table: https://github.com/nextcloud/server/pull/19002
    Header onsuccess unset Referrer-Policy
    Header always set Referrer-Policy "no-referrer"

    Header onsuccess unset X-Content-Type-Options
    Header always set X-Content-Type-Options "nosniff"

    Header onsuccess unset X-Download-Options
    Header always set X-Download-Options "noopen"

    Header onsuccess unset X-Frame-Options
    Header always set X-Frame-Options "SAMEORIGIN"

    Header onsuccess unset X-Permitted-Cross-Domain-Policies
    Header always set X-Permitted-Cross-Domain-Policies "none"

    Header onsuccess unset X-Robots-Tag
    Header always set X-Robots-Tag "none"

    Header onsuccess unset X-XSS-Protection
    Header always set X-XSS-Protection "1; mode=block"

    SetEnv modHeadersAvailable true
  </IfModule>

  # Add cache control for static resources
  <FilesMatch "\.(css|js|svg|gif|png|jpg|ico)$">
    Header set Cache-Control "max-age=15778463"
  </FilesMatch>

  # Let browsers cache WOFF files for a week
  <FilesMatch "\.woff2?$">
    Header set Cache-Control "max-age=604800"
  </FilesMatch>
</IfModule>
<IfModule mod_php7.c>
  php_value mbstring.func_overload 0
  php_value default_charset 'UTF-8'
  php_value output_buffering 0
  <IfModule mod_env.c>
    SetEnv htaccessWorking true
  </IfModule>
</IfModule>
<IfModule mod_rewrite.c>
  RewriteEngine on
  RewriteCond %{HTTP_USER_AGENT} DavClnt
  RewriteRule ^$ /remote.php/webdav/ [L,R=302]
  RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
  RewriteRule ^\.well-known/carddav /remote.php/dav/ [R=301,L]
  RewriteRule ^\.well-known/caldav /remote.php/dav/ [R=301,L]
  RewriteRule ^remote/(.*) remote.php [QSA,L]
  RewriteRule ^(?:build|tests|config|lib|3rdparty|templates)/.* - [R=404,L]
  RewriteRule ^\.well-known/(?!acme-challenge|pki-validation) /index.php [QSA,L]
  RewriteRule ^(?:\.(?!well-known)|autotest|occ|issue|indie|db_|console).* - [R=404,L]
</IfModule>
<IfModule mod_mime.c>
  AddType image/svg+xml svg svgz
  AddEncoding gzip svgz
</IfModule>
<IfModule mod_dir.c>
  DirectoryIndex index.php index.html
</IfModule>
AddDefaultCharset utf-8
Options -Indexes
<IfModule pagespeed_module>
  ModPagespeed Off
</IfModule>
#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####

ErrorDocument 403 /
ErrorDocument 404 /
<IfModule mod_rewrite.c>
  Options -MultiViews
  RewriteRule ^core/js/oc.js$ index.php [PT,E=PATH_INFO:$1]
  RewriteRule ^core/preview.png$ index.php [PT,E=PATH_INFO:$1]
  RewriteCond %{REQUEST_FILENAME} !\.(css|js|svg|gif|png|html|ttf|woff2?|ico|jpg|jpeg|map|webm|mp4|mp3|ogg|wav)$
  RewriteCond %{REQUEST_FILENAME} !/core/ajax/update\.php
  RewriteCond %{REQUEST_FILENAME} !/core/img/(favicon\.ico|manifest\.json)$
  RewriteCond %{REQUEST_FILENAME} !/(cron|public|remote|status)\.php
  RewriteCond %{REQUEST_FILENAME} !/ocs/v(1|2)\.php
  RewriteCond %{REQUEST_FILENAME} !/robots\.txt
  RewriteCond %{REQUEST_FILENAME} !/(ocm-provider|ocs-provider|updater)/
  RewriteCond %{REQUEST_URI} !^/\.well-known/(acme-challenge|pki-validation)/.*
  RewriteCond %{REQUEST_FILENAME} !/richdocumentscode(_arm64)?/proxy.php$
  RewriteRule . index.php [PT,E=PATH_INFO:$1]
  RewriteBase /
  <IfModule mod_env.c>
    SetEnv front_controller_active true
    <IfModule mod_dir.c>
      DirectorySlash off
    </IfModule>
  </IfModule>
</IfModule>

This is also something I am in need of!

I am bit baffled by the fact that - to the best of my knowledge and search-foo - there is no complete(ly) working example for caddy-v2 + nextcloud on the internet. I didn’t test the one with docker-compose labels instead of caddyfile (see my setup as a reason below), but that seems similarily incomplete too.

I have similar issues, although my setup is a bit different:

  • I run a docker(-compose)'d nextcloud, the -fpm variant (without the apache webserver in the container)
  • for now, there is a docker-composed nginx, that is the file_server (for static files) and reverse-proxy for nextcloud (and some other sites)
  • I am trying to replace this container’d nginx with a caddy on the host

Your “Access denied!” part has serious and stylistic issues! I think mine is better there.

  • serious: Your denied list of pathes is far from complete (AFAIK)!
  • quasi-serious: 403 does leak information that the requested resource exists, most example use 404 therefore.
  • stylistic, but also a maintenance burden: You should use named matcher here too, and set respond on that. This way for example, you can replace 403 with 404 at a single place!

Otherwise, I am in a bit of a bind here, as I am not versed in webserver config to put it very lightly: I very much struggle with nginx (and I would not touch apache with a ten-foot pole, partly because its config files).
There should be a fickin’ good caddy v2 example somewhere by now, that tries to faithfully replicate the example apache/nginx configs. There is an official example caddy v1 config, that seems to be a mostly equivalent rewrite of some contemporary nginx/apache config, but it seems that although caddy v1 is “deprecated”, v2 is not really there to be a good replacement - yet. Maybe it lacks just documentation-wise…

Here is my caddyfile, mostly copied from some example.
I tram the non-relevant parts and sites

example.cloud {
  # I run caddy on the host, and this is the root folder of nextcloud there
  root * /mnt/repo-base/volumes/nextcloud/html
  file_server
  encode gzip

  # This is the opinionated shorthand, that is most likely needs custom overrides
  # php_fastcgi 127.0.0.1:9001 {
  # root /var/www/html
  # }

  # This is the long form of the php_fastcgi, mostly as seen here: https://caddyserver.com/docs/caddyfile/directives/php_fastcgi#expanded-form
  route {
    # Add trailing slash for directory requests
    @canonicalPath {
      file {path}/index.php
      not path */
    }
    redir @canonicalPath {path}/ 308

    # CUSTOM ADDITION TO php_fastcgi
    # Remove trailing slashes from dav protocols
    @trailingSlashDavPath {
      path_regexp davslash /((?:remote|public)\.php/(?:web|cal|card)?dav(?:/[^/]+)?)(?:/+)$
    }
    rewrite @trailingSlashDavPath /{re.davslash.1}

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

    # Proxy PHP files to the FastCGI responder
    @phpFiles path *.php
    # Host port 9001 is mapped to nextcloud's 9000, for container'd caddy use <container_name>:9000
    reverse_proxy @phpFiles 127.0.0.1:9001 {
      transport fastcgi {
        split .php
        # CUSTOM ADDITION TO php_fastcgi
        # This is the nextcloud root as seen/mounted inside the container
        root /var/www/html
        # CUSTOM ADDITION TO php_fastcgi - not really working
        # env front_controller_active true
      }
    }
  }

  header {
  # enable HSTS
  # Strict-Transport-Security max-age=31536000;
  }

  redir /.well-known/carddav /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 /.xml
    path /3rdparty/*
    path /README
    path /config/*
    path /console.php
    path /data/*
    path /db_structure
    path /lib/*
    path /occ
    path /templates/*
    path /tests/*
  }
  respond @forbidden 404
}

www.example.cloud {
  redir https://example.cloud{uri}
}
1 Like

In case anyone wants to run caddy V2 as well as nextcloud all on the same host without docker and nextcloud in a subdirectory, then this snippet from my Caddyfile might help:

	redir /.well-known/carddav /nextcloud/remote.php/dav 301
	redir /.well-known/caldav /nextcloud/remote.php/dav 301
	redir /.well-known/webfinger /nextcloud/index.php{uri} 301
	redir /.well-known/nodeinfo /nextcloud/index.php{uri} 301

	handle /nextcloud/* {
		header {
			Referrer-Policy "no-referrer" defer
			X-Content-Type-Options "nosniff" defer
			X-Download-Options "noopen" defer
			X-Frame-Options "SAMEORIGIN" defer
			X-Permitted-Cross-Domain-Policies "none" defer
			X-Robots-Tag "none" defer
			X-XSS-Protection "1; mode=block" defer
		}
		php_fastcgi unix//usr/local/var/run/php-fpm.sock {
			try_files {path} {path}/index.php /nextcloud/index.php index.php
			env front_controller_active true
			header_down -x-powered-by
		}
		@forbidden {
			path /nextcloud/.htaccess
			path /nextcloud/data/*
			path /nextcloud/config/*
			path /nextcloud/db_*
			path /nextcloud/.xml
			path /nextcloud/README
			path /nextcloud/AUTHORS
			path /nextcloud/COPYING
			path /nextcloud/3rdparty/*
			path /nextcloud/lib/*
			path /nextcloud/templates/*
			path /nextcloud/occ
			path /nextcloud/console.php
			path /nextcloud/cron.php
		}
		respond @forbidden 404
	}

I use fastcgi via unix sockets here, this is not entirely understood by nextcloud as the proxy setup check does not pass (although it works fine), as there is no IP address to check in trusted_proxies.

1 Like

Mine looks like this and is working excpet the webfinger part

my.subdomain.com {
        root    * /var/www/nextcloud
        file_server

        php_fastcgi unix//run/php/php8.2-fpm.sock

        header {
                Strict-Transport-Security               "max-age=15552000;"
                X-Frame-Options                         "SAMEORIGIN" always
                X-Permitted-Cross-Domain-Policies       "none" always
                X-Robots-Tag                            "none" always
                X-XSS-Protection                        "1; mode=block" always
                X-Content-Type-Options                  "nosniff" always
        }

        #redir /.well-known/webfinger /public.php?service=webfinger last;
        redir /.well-known/carddav /remote.php/dav 301
        redir /.well-known/caldav /remote.php/dav 301
        redir /.well-known/host-meta /public.php?service=host-meta
        redir /.well-known/host-meta\.json /public.php?service=host-meta-json

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

i’m triying to get this to fly (non-docker nextcloud 28 in a subdirectory with caddy), but all i’m getting is a “page not found” error.

if i configure caddy to use the (sub)domain directly i can log in and use nextcloud, so i would assume the setup to be functioning in principle.

your example is missing the root and file_server directives, correct? may i ask what you did specify in your config.php file, especially regarding overwritewebroot and overwrite.cli.url?

i’m also using a custom port for nextcloud, is that something to take special care of in conjunction with a custom webroot?

I am not using nextcloud in a subdirectory any longer, it was way too much hassle. I am nowadays allocating a subdomain and a DNS CNAME record for every service running on a host. Caddy makes allocation of SSL certificates so easy that the reason for putting multiple services under subdirectories is probably moot.

But back then I had the enclosing config referring to the root of the web server, e.g.:

        root * /usr/local/var/www
        file_server

the Nextcloud installation was then under /usr/local/var/www/nextcloud. I did set the url in my config.php like this:

'overwrite.cli.url' => 'https://host.example.org/nextcloud',

I have no idea how that would have to be modified for any special port numbers, as I would suspect that having it in a subdirectory in the first place is about not needing any more ports, just use the normal http/https port and the same SSL certificate as the main directory?

thank you so much! omitting the /nextcloud suffix from the root path was exactly the thing i was missing, it works now!

for the sake of completeness here’s my working Caddyfile:

# things to replace:
# - <example.com> with the domain name
# - <port> with the port you want nextcloud to run behind
# - <subdirectory> with the subdirectory to use behind the domain name
# - </srv/example.com> with the local path to your nextcloud installation,
#   but omitting the <subdirectory> part
# - <unix//run/php/php-fpm.sock> with the local path to the php_fastcgi socket

<example.com>:<port> {
  redir /.well-known/carddav /<subdirectory>/remote.php/dav 301
  redir /.well-known/caldav /<subdirectory>/remote.php/dav 301
  redir /.well-known/webfinger /<subdirectory>/index.php{uri} 301
  redir /.well-known/nodeinfo /<subdirectory>/index.php{uri} 301

  handle /<subdirectory>/* {
    root * </srv/example.com>
    file_server

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

    php_fastcgi <unix//run/php/php-fpm.sock> {
      try_files {path} {path}/index.php /<subdirectory>/index.php index.php
      env front_controller_active true
      header_down -x-powered-by
    }

    @forbidden {
      path /<subdirectory>/.htaccess
      path /<subdirectory>/data/*
      path /<subdirectory>/config/*
      path /<subdirectory>/db_*
      path /<subdirectory>/.xml
      path /<subdirectory>/README
      path /<subdirectory>/AUTHORS
      path /<subdirectory>/COPYING
      path /<subdirectory>/3rdparty/*
      path /<subdirectory>/lib/*
      path /<subdirectory>/templates/*
      path /<subdirectory>/occ
      path /<subdirectory>/console.php
      path /<subdirectory>/cron.php
    }

    respond @forbidden 404
  }

  log {
    output file /var/log/caddy/nextcloud.log
  }
}

and this in my config.php file:

// things to replace:
// - <example.com> with the domain name
// - <port> with the port you want nextcloud to run behind
// - <subdirectory> with the subdirectory to use behind the domain name

$CONFIG = array (
  // ...
  'overwritewebroot' => '/<subdirectory>',
  'overwrite.cli.url' => 'https://<example.com>:<port>/<subdirectory>',
);

i’m running both nextcloud and seafile on a machine behind dynDNS where i can’t easily define additional subdomains. using different ports would technically be enough, however, i like to know by looking at the URL which service i’m expecting to run, so i wanted the extra subdirectory as well. thanks again for helping out!

Here is my Caddyfile config:

  header {
    Strict-Transport-Security "max-age=15552000;"
  }

  encode zstd gzip
  root * /var/www/nextcloud

  php_fastcgi unix//run/php/php-fpm.sock

  @paths {
    path /apps/*
    path /core/*
    path /dist/*
  }

  file_server @paths

  @davPaths {
    path /.well-known/carddav
    path /.well-known/caldav
  }

  redir @davPaths /remote.php/dav 301

I opted for the opposite approach rather than blacklisting directories, I whitelisted them (may not have every directory or file but nextcloud appears to be working for my needs and it is easy to add paths if more are found to be needed), seems safer as nextcloud didn’t opt for a public folder for the webroot to serve from (not sure why they decided to not or maybe weren’t aware of the practice). A public subfolder for the web server to serve would be a nice change for sure though if they wanted to do it, seems better than relying on a lot of little webserver rules and/or .htaccess (and makes running nextcloud on other webservers easier as well)

I maintain a script that installs Nextcloud in a jail in TrueNAS CORE, and I use Caddy as the web server. My script includes a few versions of the Caddyfile (depending on whether it’s using DNS or HTTP validation to get a certificate, or not getting one at all), but the basic one is here:

{
	# debug
	acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
	email youremailhere
	# default_sni yourhostnamehere
}

yourhostnamehere {
	root * /usr/local/www/nextcloud
	file_server
	log {
		output file /var/log/yourhostnamehere.log
	}

	php_fastcgi 127.0.0.1:9000 {
		env front_controller_active true
	}

	header {
		# enable HSTS
		# Strict-Transport-Security max-age=31536000;
	}

	# client support (e.g. os x calendar / contacts)
	redir /.well-known/carddav /remote.php/dav 301
	redir /.well-known/caldav /remote.php/dav 301
	redir /.well-known/webfinger /index.php/.well-known/webfinger 301
	redir /.well-known/nodeinfo /index.php/.well-known/nodeinfo 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
}

Though right now I’m encountering a problem with being able to download multiple files in a single .zip. This is being tied to a line in the nginx config that I guess needs to be translated to Caddyfile syntax:

            # Required for legacy support
            rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri;

With a bit of help translating that rewrite rule, my Caddyfile now looks like this:

{
	# debug
	acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
	email youremailhere
	# default_sni yourhostnamehere
}

yourhostnamehere {
	root * /usr/local/www/nextcloud
	file_server
	log {
		output file /var/log/yourhostnamehere.log
	}

	php_fastcgi 127.0.0.1:9000 {
		env front_controller_active true
	}

	header {
		# enable HSTS
		# Strict-Transport-Security max-age=31536000;
	}

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

	# Required for legacy
	@notlegacy {
		path *.php
		not path /index*
		not path /remote*
		not path /public*
		not path /cron*
		not path /core/ajax/update*
		not path /status*
		not path /ocs/v1*
		not path /ocs/v2*
		not path /updater/*
		not path /ocs-provider/*
		not path */richdocumentscode/proxy*
	}
	rewrite @notlegacy /index.php{uri}

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

I have the HSTS header commented out by default, as I recommend people make sure all the TLS stuff is working properly through at least one renew cycle before enabling it. I also use the LE staging server by default to minimize problems with rate limits. Both of those can obviously be changed to preference.

1 Like

Confirmed working.

Link to relevant caddy thread here