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