I've had nextcloud running through docker for quite a while. I'm not sure how long, but it's been at least 3 years.
nextcloud is running via docker compose and I'm using nginx to proxy the nextcloud docker http traffic to the internet. nginx is also proxying many other subdomains for other services, so using something else to forward 80/443 traffic would be way too much work.
Over the years, every once in a while I'll see a warning on the nextcloud Security & setup warnings page and they're normally easy to correct with some googling.
This latest warning wasn't obvious how to correct and after poking around in the documentation, I realized that the code-rot in my nginx config for nextcloud was quite putrid and decided I might as well update my nginx conf to what is recommended by nextcloud.
Nextcloud Warning:
Your web server is not properly set up to resolve "/ocm-provider/". This is most likely related to a web server configuration that was not updated to deliver this folder directly. Please compare your configuration against the shipped rewrite rules in ".htaccess" for Apache or the provided one in the documentation for Nginx at it's documentation page ↗. On Nginx those are typically the lines starting with "location ~" that need an update.
Nextcloud mentions that only Apache is officially supported, and their documentation is for running nextcloud on bare metal and not through docker. https://docs.nextcloud.com/server/27/admin_manual/installation/nginx.html Add on top of that I'm not using the office nextcloud docker image, I can't remember why but I'm sure there was a reason for the docker image I'm using.
I updated my nginx config piecemeal and commented out any code block that broke things more than they were. In the end I was able get my config to closely resemble their example config without breaking things further.
Below is my docker-compose.yml
version: '2'
networks:
default:
driver: bridge
services:
nextcloud:
image: ghcr.io/hoellen/nextcloud
restart: unless-stopped
depends_on:
- nextcloud-db
- redis
environment:
- UID=1000
- GID=1000
- UPLOAD_MAX_SIZE=10G
- APC_SHM_SIZE=128M
- OPCACHE_MEM_SIZE=128
- CRON_PERIOD=15m
- TZ=America/Vancouver
- DOMAIN=cloud.redacted.xyz
- DB_TYPE=mysql
- DB_NAME=nextcloud
- DB_USER=nextcloud
- DB_PASSWORD=redacted1234
- DB_HOST=nextcloud-db
volumes:
- ./volumes/nextcloud/data:/data
- ./volumes/nextcloud/config:/nextcloud/config
- ./volumes/nextcloud/apps:/nextcloud/apps2
- ./volumes/nextcloud/themes:/nextcloud/themes
ports:
- 8888:8888
nextcloud-db:
image: mariadb:10.6
restart: unless-stopped
command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW --innodb-read-only-compressed=OFF
volumes:
- ./volumes/nextcloud/db:/var/lib/mysql
- ./volumes/nextcloud/my.cnf:/etc/mysql/my.cnf
environment:
- MYSQL_ROOT_PASSWORD=redacted5678
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_PASSWORD=redacted1234
- TZ=America/Vancouver
redis:
image: redis:alpine
restart: unless-stopped
container_name: redis
volumes:
- ./volumes/nextcloud/redis:/data
environment:
- TZ=America/Vancouver
Here is my nginx nextcloud.conf
upstream php-handler {
server 127.0.0.1:9000;
}
map $arg_v $asset_immutable {
"" "";
default "immutable";
}
server {
listen 192.168.1.10:80;
server_name cloud.redacted.xyz;
server_tokens off;
return 301 https://$server_name$request_uri;
}
server {
listen 192.168.1.10:443 ssl http2; # managed by Certbot
server_name cloud.redacted.xyz;
ssl_certificate /etc/letsencrypt/live/cloud.redacted.xyz/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/cloud.redacted.xyz/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
server_tokens off;
client_max_body_size 512M;
client_body_timeout 300s;
fastcgi_buffers 64 4K;
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types application/atom+xml text/javascript application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
client_body_buffer_size 512k;
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Download-Options "noopen" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header X-Robots-Tag "noindex, nofollow" always;
add_header X-XSS-Protection "1; mode=block" always;
fastcgi_hide_header X-Powered-By;
include mime.types;
types {
text/javascript js mjs;
}
location = / {
if ( $http_user_agent ~ ^DavClnt ) {
return 302 /remote.php/webdav/$is_args$args;
}
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location ^~ /.well-known {
# The rules in this block are an adaptation of the rules
# in `.htaccess` that concern `/.well-known`.
location = /.well-known/carddav { return 301 /remote.php/dav/; }
location = /.well-known/caldav { return 301 /remote.php/dav/; }
location /.well-known/acme-challenge { try_files $uri $uri/ =404; }
location /.well-known/pki-validation { try_files $uri $uri/ =404; }
# Let Nextcloud's API for `/.well-known` URIs handle all other
# requests by passing them to the front-end controller.
return 301 /index.php$request_uri;
}
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; }
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; }
# location ~ \.php(?:$|/) {
# 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;
# fastcgi_split_path_info ^(.+?\.php)(/.*)$;
# set $path_info $fastcgi_path_info;
# try_files $fastcgi_script_name =404;
# include fastcgi_params;
# fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# fastcgi_param PATH_INFO $path_info;
# fastcgi_param HTTPS on;
# fastcgi_param modHeadersAvailable true; # Avoid sending the security headers twice
# fastcgi_param front_controller_active true; # Enable pretty urls
# fastcgi_pass php-handler;
# fastcgi_intercept_errors on;
# fastcgi_request_buffering off;
# fastcgi_max_temp_file_size 0;
# }
location ~ \.(?:css|js|mjs|svg|gif|png|jpg|ico|wasm|tflite|map)$ {
try_files $uri /index.php$request_uri;
add_header Cache-Control "public, max-age=15778463, $asset_immutable";
access_log off; # Optional: Don't log access to assets
location ~ \.wasm$ {
default_type application/wasm;
}
}
location ~ \.woff2?$ {
try_files $uri /index.php$request_uri;
expires 7d; # Cache-Control policy borrowed from `.htaccess`
access_log off; # Optional: Don't log access to assets
}
# Rule borrowed from `.htaccess`
# location /remote {
# return 301 /remote.php$request_uri;
# }
location / {
# try_files $uri $uri/ /index.php$request_uri;
proxy_pass http://127.0.0.1:8888;
proxy_hide_header X-XSS-Protection;
add_header X-XSS-Protection "1; mode=block" always;
}
}
If I uncomment location ~ \.php(?:$|/) {
and the closing brace or the section
location /remote {
return 301 /remote.php$request_uri;
}
then I'm receiving the following warnings in nextcloud
Your web server is not yet properly set up to allow file synchronisation, because the WebDAV interface seems to be broken. Your web server is not properly set up to resolve "/ocm-provider/". This is most likely related to a web server configuration that was not updated to deliver this folder directly. Please compare your configuration against the shipped rewrite rules in ".htaccess" for Apache or the provided one in the documentation for Nginx at it's documentation page ↗. On Nginx those are typically the lines starting with "location ~" that need an update. Your web server is not properly set up to resolve "/.well-known/webfinger". Further information can be found in the documentation ↗. Your web server is not properly set up to resolve "/.well-known/nodeinfo". Further information can be found in the documentation ↗. Your web server is not properly set up to resolve "/.well-known/caldav". Further information can be found in the documentation ↗. Your web server is not properly set up to resolve "/.well-known/carddav". Further information can be found in the documentation ↗.
If I only uncomment out try_files $uri $uri/ /index.php$request_uri;
in location / {
then I get a 500 Internal Server Error
ANY help would be very much appreciated.
Thanks for your time to ready my dribble, Dan
EDIT After some playing, I've corrected most of my code and it is very close to the original suggested code from nextcloud. I'm still getting the warning about ocm-provider, but I see an open ticket on the hoellen/nextcloud github page https://github.com/hoellen/docker-nextcloud/issues/47.
Below is what I'm currently using in my nginx config
upstream php-handler {
server 127.0.0.1:8888;
server unix:/var/run/php/php-fpm.sock;
}
map $arg_v $asset_immutable {
"" "";
default "immutable";
}
server {
listen 192.168.1.10:80;
server_name cloud.redacted.xyz;
server_tokens off;
return 301 https://$server_name$request_uri;
}
server {
listen 192.168.1.10:443 ssl http2; # managed by Certbot
server_name cloud.redacted.xyz;
ssl_certificate /etc/letsencrypt/live/cloud.redacted.xyz/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/cloud.redacted.xyz/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
server_tokens off;
client_max_body_size 512M;
client_body_timeout 300s;
fastcgi_buffers 64 4K;
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types application/atom+xml text/javascript application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
client_body_buffer_size 512k;
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Download-Options "noopen" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header X-Robots-Tag "noindex, nofollow" always;
add_header X-XSS-Protection "1; mode=block" always;
fastcgi_hide_header X-Powered-By;
include mime.types;
types {
text/javascript js mjs;
}
index index.php index.html /index.php$request_uri;
location = / {
if ( $http_user_agent ~ ^DavClnt ) {
return 302 /remote.php/webdav/$is_args$args;
}
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location ^~ /.well-known {
# The rules in this block are an adaptation of the rules
# in `.htaccess` that concern `/.well-known`.
location = /.well-known/carddav { return 301 /remote.php/dav/; }
location = /.well-known/caldav { return 301 /remote.php/dav/; }
location /.well-known/acme-challenge { try_files $uri $uri/ =404; }
location /.well-known/pki-validation { try_files $uri $uri/ =404; }
# Let Nextcloud's API for `/.well-known` URIs handle all other
# requests by passing them to the front-end controller.
return 301 /index.php$request_uri;
}
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; }
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; }
location ~ \.php(?:$|/) {
# 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;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
set $path_info $fastcgi_path_info;
try_files $fastcgi_script_name =404 @proxy;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $path_info;
fastcgi_param HTTPS on;
fastcgi_param modHeadersAvailable true; # Avoid sending the security headers twice
fastcgi_param front_controller_active true; # Enable pretty urls
fastcgi_pass php-handler;
fastcgi_intercept_errors on;
fastcgi_request_buffering off;
fastcgi_max_temp_file_size 0;
}
location ~ \.(?:css|js|mjs|svg|gif|png|jpg|ico|wasm|tflite|map)$ {
try_files $uri /index.php$request_uri @proxy;
add_header Cache-Control "public, max-age=15778463, $asset_immutable";
access_log off; # Optional: Don't log access to assets
location ~ \.wasm$ {
default_type application/wasm;
}
}
location ~ \.woff2?$ {
try_files $uri /index.php$request_uri @proxy;
expires 7d; # Cache-Control policy borrowed from `.htaccess`
access_log off; # Optional: Don't log access to assets
}
# Rule borrowed from `.htaccess`
location /remote {
return 301 /remote.php$request_uri;
}
location / {
try_files $uri $uri/ /index.php$request_uri @proxy;
}
location @proxy {
proxy_pass http://php-handler;
proxy_hide_header X-XSS-Protection;
add_header X-XSS-Protection "1; mode=block" always;
}
}
I've managed to resolve this problem in my image, have a look at this Nginx config.
For me these two clauses were critical:
location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode\/proxy)\.php(?:$|\/) {
include /etc/nginx/fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param modHeadersAvailable true;
fastcgi_param front_controller_active true;
fastcgi_pass unix:/php/run/php-fpm.sock;
fastcgi_intercept_errors on;
fastcgi_request_buffering off;
fastcgi_read_timeout 1200;
}
location ~ ^\/(?:updater|ocs-provider)(?:$|\/) {
try_files $uri/ =404;
index index.php;
}
In both clauses you must change oc[ms]
regex fragment to ocs
.