Content-Security-Policy issue

Hi everyone,
I’m building up my home lab, especially with NC and I’m facing problems with “Content-Security-Policy” in my browser.
I know this topic has already been discussed several times but I haven’t found a solution to my problem after reading posts on this subject and try proposed solutions.


The issue I’m facing

I can’t install any applications from the WebUI.
It throws a message:

This app cannot be enabled because it makes the server unstable

My browser console returns :

Refused to connect to ‘http://cloud.domain.tld/apps/files/’ because it violates the following Content Security Policy directive: “connect-src ‘self’”.


Config overview

Docker-compose

As I said, I use Ansible for deployment. I’ve “translated” it into docker-compose to make it easier to understand for people who aren’t used to using Ansible.

version: '3'

services:
  db_nextcloud:
    image: 'postgres:16.3-alpine'
    restart: 'unless-stopped'
    volumes:
      - "~/docker/nextcloud/postgres/init/:/docker-entrypoint-initdb.d/"
    environment:
      - POSTGRES_USER = '<postgres_user>'
      - POSTGRES_PASSWORD = '<postgres_pwd>'
      - POSTGRES_DB = 'nextcloud'
    networks:
      - nextcloud_net
      - db_net 

  redis_nextcloud:
    image: 'redis:7.2.4-alpine'
    restart: 'unless-stopped'
    ...
    networks:
      - nextcloud_net

 nginx_nextcloud:
    image: 'nginx:1.26.0-alpine'
    restart: 'unless-stopped'
    volumes:
      - ~/docker/nextcloud/nginx/conf:/etc/nginx/conf.d/
      - ~/docker/nextcloud/nextcloud/html:/var/www/html:ro
    networks:
      - traefik_proxy
      - nextcloud_net

  nextcloud:
    image: 'nextcloud:29.0.0-fpm-alpine'
    restart: 'unless-stopped'
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ~/docker/nextcloud/nextcloud/html:/var/www/html
    environment:
      - NEXTCLOUD_ADMIN_USER =  '<user.name>'
      - NEXTCLOUD_ADMIN_PASSWORD = '<user.pass>'
      - POSTGRES_HOST = 'db_nextcloud'
      - POSTGRES_DB = 'nextcloud'
      - POSTGRES_USER = '<postgres_user>'
      - POSTGRES_PASSWORD = '<postgres_pwd>'
      - REDIS_HOST = 'redis_nextcloud'
      - REDIS_HOST_PORT = '<redis.port>'
      - SMTP_HOST = '<smtp.server>'
      - SMTP_PORT = '<smtp.port>'
      - SMTP_AUTHTYPE = LOGIN
      - SMTP_NAME = '<smtp.user>'
      - SMTP_PASSWORD = '<smtp.pass>'
      - TRUSTED_PROXIES = '172.18.0.254' # Address of traefik docker 
      - NEXTCLOUD_TRUSTED_DOMAINS = 'cloud.domain.tld'
      - OVERWRITEPROTOCOL = 'https'
      - OVERWRITECLIURL = 'https://cloud.domain.tld'
      - OVERWRITEHOST = 'cloud.domain.tld'
    networks:
      - traefik_proxy
      - nextcloud_net

Traefik static configuration
api=true
log=true
log.level=DEBUG
accessLog=true
accessLog.filePath=/traefik.log
accessLog.bufferingSize=100
providers.docker=true
providers.docker.endpoint=unix:///var/run/docker.sock
providers.docker.exposedByDefault=false
providers.docker.network=traefik_proxy
providers.file.directory=/rules
providers.file.watch=true
entryPoints.http.address=:80
entryPoints.https.address=:443
entrypoints.https.http.tls.certresolver=letsencrypt
certificatesResolvers.letsencrypt.acme.email='<user.mail>'
certificatesResolvers.letsencrypt.acme.storage=/acme.json
certificatesresolvers.letsencrypt.acme.tlschallenge=true
Traefik dynamic configuration

I set up dynamic configuration with docker labels. To make things easier to understand, I’ve gathered the relevant parameters in a TOML file.

[http.routers]
  [http.routers.http-catchall]
    entryPoints = ["http"]
    rule = "HostRegexp(`{host:.+}`)"
    middlewares = ["redirect-to-https"]

  [http.routers.nextcloud-rtr]
    entryPoints = ["http", "https"]
    rule = "Host(`cloud.domain.tld`)"
    service = "nextcloud-svc"
    middlewares = ["nextcloud-chain"]
    [http.routers.nextcloud-rtr.tls]
      certResolver = "letsencrypt"

[http.middlewares]
  [http.middlewares.redirect-to-https.redirectScheme]
    scheme = "https"

  [http.middlewares.nextcloud-chain]
    [http.middlewares.nextcloud-chain.chain]
      middlewares = [ "chain-authelia", "middlewares-redirectDAV"]

  [http.middlewares.chain-no-auth]
    [http.middlewares.chain-no-auth.chain]
      middlewares = [ "middlewares-rate-limit", "middlewares-secure-headers"]

  [http.middlewares.chain-authelia]
    [http.middlewares.chain-authelia.chain]
      middlewares = [ "middlewares-rate-limit", "middlewares-secure-headers", "middlewares-authelia"]

  [http.middlewares.middlewares-rate-limit]
    [http.middlewares.middlewares-rate-limit.rateLimit]
      average = 100
      burst = 50

  [http.middlewares.middlewares-secure-headers]
    [http.middlewares.middlewares-secure-headers.headers]
      accessControlAllowMethods= ["GET", "OPTIONS", "PUT"]
      accessControlMaxAge = 100 
      hostsProxyHeaders = ["X-Forwarded-Host"] 
      sslRedirect = true 
      stsSeconds = 63072000 
      stsIncludeSubdomains = true 
      stsPreload = true 
      forceSTSHeader = true 
      customFrameOptionsValue = "SAMEORIGIN" 
      contentTypeNosniff = true 
      browserXssFilter = true 
      referrerPolicy = "same-origin" 
      permissionsPolicy = "camera 'none'; geolocation 'none'; microphone 'none'; payment 'none'; usb 'none'; vr 'none';" 
      [http.middlewares.middlewares-secure-headers.headers.customResponseHeaders]
        X-Robots-Tag = "none,noarchive,nosnippet,notranslate,noimageindex,"
        server = "" 

  [http.middlewares.middlewares-authelia]
    [http.middlewares.middlewares-authelia.forwardAuth]
      address = "http://<authelia_host>:<authelia_port>/api/authz/forward-auth"
      trustForwardHeader = true
      authResponseHeaders = ["Remote-User", "Remote-Groups"]

  [http.middlewares.middlewares-redirectDAV]
    [http.middlewares.middlewares-redirectDAV.redirectRegex]
      regex = "https://(.*)/.well-known/(?:card|cal)dav"
      replacement = "https://$${1}/remote.php/dav"
      permanent = true

[http.services]
  [http.services.nextcloud-svc.loadBalancer]
    [http.services.nextcloud-svc.loadBalancer.servers]
      url = "http://<nginx_ip>:80"

Nginx configuration

I took the nginx.conf file that is provided with the docker image

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log debug;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" '
                      '"$http_x_forwarded_proto" "$http_x_forwarded_host"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;
    map $arg_v $asset_immutable {
        "" "";
    default "immutable";
    }

    upstream php-handler {
        server <nextcloud_host>:9000;
    }

    server {
        listen 80;
        client_max_body_size 512M;
        client_body_timeout 300s;
        fastcgi_buffers 64 4K;
        client_body_buffer_size 512k;
        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;
        add_header Referrer-Policy                      "no-referrer"       always;
        add_header X-Content-Type-Options               "nosniff"           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;
        root /var/www/html;
        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 {
            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; }
            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(?:$|/) {
            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 ~* \.(?:js|mjs|map)$ {
            types {
                text/javascript js mjs;
                application/json map;
            }
            try_files $uri /index.php$request_uri;
            add_header Cache-Control "public, max-age=15778463, $asset_immutable";
            access_log off;
        }
        location ~ \.(?:css|svg|gif|png|jpg|ico|wasm|tflite|map|ogg|flac)$ {
            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
        }
        location /remote {
            return 301 /remote.php$request_uri;
        }
        location / {
            try_files $uri $uri/ /index.php$request_uri;
        }
    }
}


Nextcloud configuration
<?php
$CONFIG = array (
  'memcache.local' => '\\OC\\Memcache\\APCu',
  'apps_paths' =>
  array (
    0 =>
    array (
      'path' => '/var/www/html/apps',
      'url' => '/apps',
      'writable' => false,
    ),
    1 =>
    array (
      'path' => '/var/www/html/custom_apps',
      'url' => '/custom_apps',
      'writable' => true,
    ),
  ),
  'memcache.distributed' => '\\OC\\Memcache\\Redis',
  'memcache.locking' => '\\OC\\Memcache\\Redis',
  'redis' =>
  array (
    'host' => '<redis_host>',
    'password' => '<redis_pwd>',
    'port' => <redis_port>,
  ),
  'overwriteprotocol' => 'https',                    # <- tried to remove
  'overwrite.cli.url' => 'https://cloud.domain.tld', # <- tried to remove
  'overwritewebroot' => '/',                         # <- tried to remove
  'overwritehost' => 'cloud.domain.tld',             # <- tried to remove
  'trusted_proxies' =>
  array (
    0 => '172.18.0.254',
  ),
  'upgrade.disable-web' => true,
  'passwordsalt' => '<passwordsalt>',
  'secret' => '<secret>',
  'trusted_domains' =>
  array (
    0 => 'localhost',
    1 => 'cloud.domain.tld',
  ),
  'datadirectory' => '/var/www/html/data',
  'dbtype' => 'pgsql',
  'version' => '29.0.0.19',
  'dbname' => 'nextcloud',
  'dbhost' => 'db_nextcloud',
  'dbport' => '',
  'dbtableprefix' => 'oc_',
  'dbuser' => 'oc_<postgres_user>',
  'dbpassword' => '<postgres_pwd>',
  'installed' => true,
  'instanceid' => '<instanceid>',
);


Relevant logs

Nginx logs
2024/05/15 13:13:34 [notice] 55#55: *1182 "^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode\/proxy)" does not match "/index.php/settings/apps/enable", client: 172.18.0.254, server: , request: "POST /settings/apps/enable HTTP/1.1", host: "cloud.domain.tld", referrer: "https://cloud.domain.tld/settings/apps/featured/calendar"
172.18.0.254 - - [15/May/2024:13:13:34 +0200] "POST /settings/apps/enable HTTP/1.1" 200 52 "https://cloud.domain.tld/settings/apps/featured/calendar" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" "<my_ip>" "https" "cloud.domain.tld"
172.18.0.254 - - [15/May/2024:13:13:34 +0200] "GET /apps/files HTTP/1.1" 301 169 "https://cloud.domain.tld/settings/apps/featured/calendar" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" "<my_ip>" "https" "cloud.domain.tld"
2024/05/15 13:13:34 [notice] 58#58: *1205 "^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode\/proxy)" does not match "/ocs/v2.php/core/navigation/apps", client: 172.18.0.254, server: , request: "GET /ocs/v2.php/core/navigation/apps?format=json HTTP/1.1", host: "cloud.domain.tld", referrer: "https://cloud.domain.tld/settings/apps/featured/calendar"
2024/05/15 13:13:34 [notice] 55#55: *1182 "^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode\/proxy)" does not match "/index.php/settings/apps/disable", client: 172.18.0.254, server: , request: "POST /settings/apps/disable HTTP/1.1", host: "cloud.domain.tld", referrer: "https://cloud.domain.tld/settings/apps/featured/calendar"
172.18.0.254 - - [15/May/2024:13:13:35 +0200] "POST /settings/apps/disable HTTP/1.1" 200 22 "https://cloud.domain.tld/settings/apps/featured/calendar" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" "<my_ip>" "https" "cloud.domain.tld"
172.18.0.254 - - [15/May/2024:13:13:35 +0200] "GET /ocs/v2.php/core/navigation/apps?format=json HTTP/1.1" 200 338 "https://cloud.domain.tld/settings/apps/featured/calendar" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" "<my_ip>" "https" "cloud.domain.tld"
Traefik logs
<my_ip> - - [15/May/2024:11:20:54 +0000] "POST /settings/apps/enable HTTP/2.0" 200 52 "-" "-" 25739 "https-nextcloud-rtr@docker" "http://<nginx_ip>:80" 96ms
<my_ip> - - [15/May/2024:11:20:54 +0000] "GET /apps/files HTTP/2.0" 301 169 "-" "-" 25740 "https-nextcloud-rtr@docker" "http://<nginx_ip>:80" 8ms
<my_ip> - - [15/May/2024:11:20:54 +0000] "POST /settings/apps/disable HTTP/2.0" 200 22 "-" "-" 25741 "https-nextcloud-rtr@docker" "http://<nginx_ip>:80" 210ms
<my_ip> - - [15/May/2024:11:20:54 +0000] "GET /ocs/v2.php/core/navigation/apps?format=json HTTP/2.0" 200 338 "-" "-" 25742 "https-nextcloud-rtr@docker" "http://<nginx_ip>:80" 257ms

What I’ve tried :

  • Switch from chain-authelia middleware to chain-no-auth middleware in Traefik dynamic configuration ← No changes :x:
[http.middlewares.nextcloud-chain]
  [http.middlewares.nextcloud-chain.chain]
    middlewares = [ "chain-no-auth", "middlewares-redirectDAV"]
  • Add custom request header in traefik dynamic configuration ← No changes :x:
[http.middlewares.middlewares-secure-headers.headers.customrequestheaders]
  X-Forwarded-Proto = "https"
  • Install apps through occ command ← Apps are installed properly AFAIK :white_check_mark: So the issue seems to come from how the WebUI sends the request.
  • Play with the overwrite* parameters in the nextcloud config.php file ← No changes :x:
  • Downgrate Nextcloud to an older version (28.0.5-fpm-alpine) ← No changes :x:

What I notice

  • The address in the CSP error message uses HTTP instead of HTTPS even if HTTPS is forced through 'overwriteprotocol' => 'https'
  • When I try to access http://cloud.domain.tld/apps/files/ in my browser. Traefik redirects me to https://cloud.domain.tld/apps/files/ and I’m able to access the file page
  • I have several apps that are running properly behind Traefik

I don’t see anything super obvious offhand, but do make sure you’re using occ config:list system to look at your parsed/running config rather than just looking at config.php. The Docker image uses multiple config files. You may not be looking at your full config otherwise.

EDIT: Now that I think about it, you may be hitting this[1]. You can probably address it in your config, but there’s also a workaround implemented[2] that’ll be in the upcoming 29.0.1 maintenance release[3].

[1] [Bug]: Can't enable or disable apps after upgrade to v28.0.4 · Issue #44685 · nextcloud/server · GitHub
[2] fix(settings): Save one HTTP request on enabling an app by susnux · Pull Request #44939 · nextcloud/server · GitHub
[3] [stable29] fix(settings): Save one HTTP request on enabling an app by backportbot[bot] · Pull Request #45204 · nextcloud/server · GitHub

See [Bug]: Can't enable or disable apps after upgrade to v28.0.4 · Issue #44685 · nextcloud/server · GitHub . Traefik doesn’t support rewriting the Location header by default. Seemingly this is now fixed, lets wait for the .1 version.
In the meantime you can activate the apps by using php occ app:install

Thanks for your answers !

Yep you’re right Here is what the command returns.
I don’t see anything super obvious either, except maybe the “\” but I think NC interprets them as esc chars.

config:list system
{
    "system": {
        "memcache.local": "\\OC\\Memcache\\APCu",
        "apps_paths": [
            {
                "path": "\/var\/www\/html\/apps",
                "url": "\/apps",
                "writable": false
            },
            {
                "path": "\/var\/www\/html\/custom_apps",
                "url": "\/custom_apps",
                "writable": true
            }
        ],
        "memcache.distributed": "\\OC\\Memcache\\Redis",
        "memcache.locking": "\\OC\\Memcache\\Redis",
        "redis": {
            "host": "***REMOVED SENSITIVE VALUE***",
            "password": "***REMOVED SENSITIVE VALUE***",
            "port": 6379
        },
        "overwritehost": "cloud.domain.tld",
        "overwriteprotocol": "https",
        "overwrite.cli.url": "https:\/\/cloud.domain.tld",
        "overwritewebroot": "\/",
        "trusted_proxies": "***REMOVED SENSITIVE VALUE***",
        "upgrade.disable-web": true,
        "passwordsalt": "***REMOVED SENSITIVE VALUE***",
        "secret": "***REMOVED SENSITIVE VALUE***",
        "trusted_domains": [
            "localhost",
            "cloud.domain.tld"
        ],
        "datadirectory": "***REMOVED SENSITIVE VALUE***",
        "dbtype": "pgsql",
        "version": "29.0.0.19",
        "dbname": "***REMOVED SENSITIVE VALUE***",
        "dbhost": "***REMOVED SENSITIVE VALUE***",
        "dbport": "",
        "dbtableprefix": "oc_",
        "dbuser": "***REMOVED SENSITIVE VALUE***",
        "dbpassword": "***REMOVED SENSITIVE VALUE***",
        "installed": true,
        "instanceid": "***REMOVED SENSITIVE VALUE***"
    }
}

Yep I’ve already read those posts on github. I’ll wait for the next version then and in the meantime make a small script to install apps via the hooks that can be used in the docker image.


I’ll still keep testing and update this post if I ever find a solution. It could always help other people using roughly the same architecture.

After running a few tests, I realized that something interesting was going on.
By disabling pretty urls, everything seems to work properly.
(i.e. by commenting out the line #fastcgi_param front_controller_active true).

So I wonder if there’s a mistake in the nginx rules about rewriting urls?

To be honest, I’m not an nginx expert, so if any of you have any ideas, I’d be interested.

Finally I managed to make it work. I’m not sure if this is the best way to solve the problem, but here’s what I did:

Add this at the end of the nginx conf file :

location ~ ^/apps/files {
        rewrite ^/(?!index) /index.php$request_uri;
    }

This way it works with pretty urls and I no longer have CSP issues.


Here is the whole config file :
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log debug;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" '
                      '"$http_x_forwarded_proto" "$http_x_forwarded_host"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;
    map $arg_v $asset_immutable {
        "" "";
    default "immutable";
    }

    upstream php-handler {
        server <nextcloud_host>:9000;
    }

    server {
        listen 80;
        client_max_body_size 512M;
        client_body_timeout 300s;
        fastcgi_buffers 64 4K;
        client_body_buffer_size 512k;
        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;
        add_header Referrer-Policy                      "no-referrer"       always;
        add_header X-Content-Type-Options               "nosniff"           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;
        root /var/www/html;
        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 {
            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; }
            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(?:$|/) {
            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 ~* \.(?:js|mjs|map)$ {
            types {
                text/javascript js mjs;
                application/json map;
            }
            try_files $uri /index.php$request_uri;
            add_header Cache-Control "public, max-age=15778463, $asset_immutable";
            access_log off;
        }
        location ~ \.(?:css|svg|gif|png|jpg|ico|wasm|tflite|map|ogg|flac)$ {
            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
        }
        location /remote {
            return 301 /remote.php$request_uri;
        }
        location ~ ^/apps/files {
            rewrite ^/(?!index) /index.php$request_uri;
        }
        location / {
            try_files $uri $uri/ /index.php$request_uri;
        }
    }
}

If ever this helps any one out there…

1 Like

YES!
in traefik this would look like:

  - "traefik.http.middlewares.nctredirect.redirectregex.regex=(http.?://)my.nc.de/(apps/files.*)"
  - "traefik.http.middlewares.nctredirect.redirectregex.replacement=https://my.nc.de/index.php/$${2}"
  - traefik.http.middlewares.nctredirect.redirectregex.permanent=true