Docker image base url webroot modification with HTTP not working unless container content is modified

Hello nextcloud community,

I have a hell of a hard time configuring nextcloud docker image. Please bear in mind I have a rudimentary knowledge of how apache is serving files for a webserver.

I intend to use a NGINX reverse proxy, so I need to have a baseURL to serve different applications on the same server. I don’t want to manage the SSL/https at the nextcloud app level but at the nginx level.

So http will do for this nextcloud and it is the aim.

As a result, I wanted to append /nc/ to the base URL (access to http://example.com/nc/ instead of http://example.com/).

To make it easier and as part of this post, I am not considering the proxy for now, I just want to have owncloud working using http and being able to access it through http://example.com/nc/. So there is NO proxy involved as part of the following:

Here is the relatively simple docker-compose.yml :

volumes:
  nextcloud-data:
  nextcloud-db:

networks:
  frontend:
    # add this if the network is already existing!
    # external: true
  backend:

services:
  nextcloud-app:
    image: nextcloud
    restart: always
    ports:
      - "80:80"
      - "81:81"
      - "443:443"
    volumes:
      - /media/nextcloud-data:/var/www/html
      - /home/user1/Docker/nextcloud-christian-lempa/logs:/var/log/apache2
    environment:
      - MYSQL_PASSWORD=*******
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_HOST=nextcloud-db
      - NEXTCLOUD_TRUSTED_DOMAINS=example.com
      - OVERWRITEHOST=example.com
      - OVERWRITEPROTOCOL=http
      - OVERWRITEWEBROOT=/nc

    networks:
      - frontend
      - backend

  nextcloud-db:
    image: mariadb
    restart: always
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    volumes:
      - /media/nextcloud-db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=*******
      - MYSQL_PASSWORD=*******
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      start_period: 10s
      interval: 10s
      timeout: 5s
      retries: 3

    networks:
      - backend

Following this it should be working from my understanding.
But it doesn’t because apache2 is searching files in /var/www/html/nc.

So I had to manually create a symlink in the container:
ln -s /var/www/html/ /var/www/html/nc
chown www-data:root -h /var/www/html/nc

Now I can access the first page, asking me to define an admin user and password. Once these two fields are defined, here come the second, trickier issue :
We can see the page stating “installing”…But then I get redirected to a page http://example.com/nc/index.php/core/apps/recommended stating :

Recommended apps
Could not fetch list of apps from the App Store.

The network tab of chrome developer tools shows the following errors :

10Refused to apply style from '<URL>' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
recommended:1 Refused to apply style from 'http://example.com/nc/apps/theming/theme/light-highcontrast.css?plain=0&v=21421e36' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
recommended:1 Refused to apply style from 'http://example.com/nc/apps/theming/theme/default.css?plain=1&v=21421e36' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
recommended:1 Refused to apply style from 'http://example.com/nc/apps/theming/theme/dark.css?plain=0&v=21421e36' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
recommended:1 Refused to apply style from 'http://example.com/nc/apps/theming/theme/dark-highcontrast.css?plain=0&v=21421e36' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
recommended:1 Refused to apply style from 'http://example.com/nc/apps/theming/theme/light.css?plain=1&v=21421e36' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
recommended:1 Refused to apply style from 'http://example.com/nc/apps/theming/theme/light.css?plain=0&v=21421e36' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
recommended:1 Refused to apply style from 'http://example.com/nc/apps/theming/theme/dark.css?plain=1&v=21421e36' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
recommended:1 Refused to apply style from 'http://example.com/nc/apps/theming/theme/light-highcontrast.css?plain=1&v=21421e36' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
recommended:1 Refused to apply style from 'http://example.com/nc/apps/theming/theme/opendyslexic.css?plain=0&v=21421e36' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
recommended:1 Refused to apply style from 'http://example.com/nc/apps/theming/theme/dark-highcontrast.css?plain=1&v=21421e36' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
recommended:23 
        
        
       GET http://example.com/nc/js/core/merged-template-prepend.js?v=9ae81861-0 net::ERR_ABORTED 404 (Not Found)
recommended:1 Refused to execute script from 'http://example.com/nc/js/core/merged-template-prepend.js?v=9ae81861-0' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
session-heartbeat.js:103 session heartbeat polling started
RecommendedApps.vue:140 
        
        
       GET http://example.com/nc/settings/apps/list 404 (Not Found)
(anonymous) @ xhr.js:258
xhr @ xhr.js:49
p @ dispatchRequest.js:51
_request @ Axios.js:170
request @ Axios.js:40
a.A.forEach.w.<computed> @ Axios.js:196
(anonymous) @ bind.js:5
mounted @ RecommendedApps.vue:140
un @ vue.runtime.esm.js:3033
Un @ vue.runtime.esm.js:4048
e @ vue.runtime.esm.js:3921
ja.$mount @ vue.runtime.esm.js:8797
43474 @ recommendedapps.js:40
i @ bootstrap:19
(anonymous) @ core-recommendedapps.js?v=9ae81861-0:2
i.O @ chunk loaded:25
(anonymous) @ core-recommendedapps.js?v=9ae81861-0:2
(anonymous) @ core-recommendedapps.js?v=9ae81861-0:2
Show 11 more frames
Show less
RecommendedApps.vue:148 [ERROR] core: could not fetch app list {app: 'core', uid: 'admin', level: 2, error: r}
value @ ConsoleLogger.js:74
value @ ConsoleLogger.js:100
mounted @ RecommendedApps.vue:148
await in mounted (async)
un @ vue.runtime.esm.js:3033
Un @ vue.runtime.esm.js:4048
e @ vue.runtime.esm.js:3921
ja.$mount @ vue.runtime.esm.js:8797
43474 @ recommendedapps.js:40
i @ bootstrap:19
(anonymous) @ core-recommendedapps.js?v=9ae81861-0:2
i.O @ chunk loaded:25
(anonymous) @ core-recommendedapps.js?v=9ae81861-0:2
(anonymous) @ core-recommendedapps.js?v=9ae81861-0:2
Show 6 more frames
Show less
session-heartbeat.js:86 
        
        
       GET http://example.com/nc/csrftoken 404 (Not Found)
send @ jquery.js:9940
ajax @ jquery.js:9521
C.each.C.<computed> @ jquery.js:9677
(anonymous) @ session-heartbeat.js:86
Pt @ session-heartbeat.js:93
setInterval (async)
Ot @ session-heartbeat.js:101
(anonymous) @ session-heartbeat.js:157
Ze @ init.js:111
(anonymous) @ main.js:45
Show 3 more frames
Show less
session-heartbeat.js:96 session heartbeat failed {readyState: 4, getResponseHeader: ƒ, getAllResponseHeaders: ƒ, setRequestHeader: ƒ, overrideMimeType: ƒ, …}
Pt @ session-heartbeat.js:96
setInterval (async)
Ot @ session-heartbeat.js:101
(anonymous) @ session-heartbeat.js:157
Ze @ init.js:111
(anonymous) @ main.js:45
session-heartbeat.js:86 
        
        
       GET http://example.com/nc/csrftoken 404 (Not Found)
send @ jquery.js:9940
ajax @ jquery.js:9521
C.each.C.<computed> @ jquery.js:9677
(anonymous) @ session-heartbeat.js:86
Pt @ session-heartbeat.js:93
setInterval (async)
Ot @ session-heartbeat.js:101
(anonymous) @ session-heartbeat.js:157
Ze @ init.js:111
(anonymous) @ main.js:45
Show 3 more frames
Show less
session-heartbeat.js:96 session heartbeat failed {readyState: 4, getResponseHeader: ƒ, getAllResponseHeaders: ƒ, setRequestHeader: ƒ, overrideMimeType: ƒ, …}

I won’t get into too much details, but I spent more than 10 hours of troubleshooting to understand it was due to one file in the container :
-rw-r–r-- 1 www-data www-data 3954 Jul 29 13:36 /var/www/html/.htaccess

Once this one is removed, I can access the apps, and login as expected. Without removing it, I had all sorts of 404 errors (permission related it seems in the end).

I am nevertheless aware that this file /var/www/html/.htaccess is there for a reason, for a security reason.

I am also aware that nextcloud is designed to work with https and an SSL cert. But I don’t have a fixed domain (I use a no-ip domain as well as a self signed cert for my proxy to access over internet), so the only way I got it to work was by running nextcloud in http and then get https traffic through my reverse proxy for security.

Would it be fair to request the docker image to be modified so that it does the creating a symlink and editing of /var/www/html/.htaccess automatically when using the parameter OVERWRITEWEBROOT in docker-compose file? As it is open source, I am willing to do it, it shouldn’t be too complicated now that I pinpointed the problem (provided someone here is a pro at apache2 rewrite rules).

So the only problem remaining here is that I am not sure how to rewrite all the rewrite rules so that they integrate the /nc/ or whatever is passed as an argument to OVERWRITEWEBROOT when using docker-compose.

Indeed, if no OVERWRITEWEBROOT parameter is passed, I can setup the http nextcloud without symlink and without removing or editing .htaccess.

If this is not considered a bug or at least a feature request, is the container not supposed to work with the parameters I have passed when creating the container with docker-compose?

Thanks!

I can’t say if something is wrong with containers but often issues arise because people add configuration options/ENV one-by-one without properly recreating the container (docker compose down in case of compose) many of the variables are used only once when the container is created and have effect later.

personally I have no experience running NC with overwritewebroot but this config is common so I don’t expect general issues.

the scenario where the reverse proxy terminates TLS connections and connects to the backend using plain https is called TLS offloading. Running NC behind reverse proxy with TLS offloading is common as well and works well with letsencrypt certificates - you only need a valid public DNS (personally I use nc.mydomain.tld as CNAME to my myfritz public DNS).

But your system configuration is wrong for this scenario, it must be

as reverse proxy parameters are intended to construct the “external” URL which is exposed to the client (and not the one used by a reverse proxy to access the back-end application).

As you mention the system work “better” after removing .htaccess you might need to perfrom htaccess update by running occ maintenance:update:htaccess or settings NEXTCLOUD_INIT_HTACCESS varaible before container re-init.

I don’t get why you expose ports 81 and 443 as well as you don’t intend to use them but it should not harm (as long it doesn’t conflict with the reverse proxy which requires the ports).

I would recommend you start with Docker .examples - insecure is exactly what you are looking for, but there are examples with NGINX as well so you can learn from there.

1 Like

The Docker image is not a subdirectory based installation of Nextcloud.

It sounds like you’re simply trying to make your Nextcloud instance available via a subfolder URL. That is done on your proxy. It does not change how Nextcloud gets installed.

The only changes you’ll make, if any, are to tell Nextcloud it’s behind a proxy and the correct URLs/etc to use via the OVERWRITE* values.

There is no need to modify the image/etc.

1 Like

Hello wwe,

Thank you for your time and the help provided.

For the exposed port 81 it is a leftover from the initial template, 443 was left there as I was testing and got issues so wanted to open as much as possible to rule out any port related issue. These two will be removed when I will get all working.

In the context of testing I explicitly chose HTTP not HTTPS for OVERWRITEPROTOCOL, as mentioned previously, proxy is out of the picture for testing. I could read in the documentation that it is to be set to https as it is explained in nextcloud documentation for https reverse proxy configurations. I nevertheless don’t understand why this is needed in my config (nextcloud sending http requests that are poxied as https anyway). Could you expand on this?

My way of thinking was to get a running http nextcloud server with a subpath.

I did see the option NEXTCLOUD_INIT_HTACCESS in the documentation. I am far from being an expert and I don’t understand what it is doing. I mean, yes it updates the .htaccess file, but it updates its content after checking what changes? I have just started the container, there is no update. Is it needed when you replace manually an updated .htaccess file? If so, I still don’t understand, as apache is taking into account my changes after I restart the container.

I checked the examples provided. The closest and most relevant is this one : https://github.com/nextcloud/docker/tree/master/.examples/docker-compose/with-nginx-proxy/mariadb/apache and they are not providing help on how to configure nextcloud rewrite or overwrite rules with examples, which is really what I am after now (to avoid the symlink and remove .htaccess, which is the only explicitly explained solution I found so far).

Hello Jtr,

Thank you for your answer. I will try to detail my answer and make it practical with examples, I am pretty sure the problem I have is encountered by several people.
Hopefully, by the end of our exchange this will be a sort of a tutorial as well as a help for me to resolve the issue, 2 birds with one stone :slight_smile:

Please bear in mind I have several apps. With each their own subfolder (one is example.com/nc/ another one is example.com/mymusic/ for example).

How should the URL ‘workflow’ happens according to you? The answer provided is too abstract and not hands-on enough for it to be useful with my understanding of webservers.

From your answer I understand it should be something like :

Client (Request https://example.com/nc/) →
Nginx Proxy (a proxy_pass rule changes https://example.com/nc/ to http://nextcloudcontainerip/nc) →
Nextcloud apache OVERWRITE rule (receives the request and a OVERWRITE* rule will rewrite from http://nextcloudcontainerip/nc to http://nextcloudcontainerip/ before sending it for processing.) →
Apache nextcoud server (processing it and forwards client to http://nextcloudcontainerip/index.php/login) →
Nextcloud apache OVERWRITE rule (using the same OVERWRITE* rule will rewrite http://nextcloudcontainerip/index.php/login to http://nextcloudcontainerip/nc/index.php/login before sending it →
Nginx Proxy (a proxy_pass rule changes http://nextcloudcontainerip/nc/index.php/login to https://example.com/nc/index.php/login) →
Client (Receives https://example.com/nc/index.php/login)

Another issue that adds up if I consider your solution of not touching the nextcloud container folders like I did with my symlink is the following : How to handle relative urls correctly with a nginx reverse proxy - Server Fault

The problem is basically that using a proxy_pass directive won’t rewrite HTML code and therefor relative URL’s to for instance a img src="/assets/image.png" won’t magically change to img src="/bbb/assets/image.png" .

So provided that my understanding is correct, what would be the actual solution? I feel like it is not as easy as it is suggested and that what I did was the easiest so far.

Thank you again for your help!

1 Like

Nginx proxy_pass destination should just be http://nextcloudcontainerip[1] in your example.

The OVERWRITEWEBROOT (really overwritewebroot from Nextcloud Server; this isn’t Docker image specific) would be set to /nc in your example).

The subdirectory is only at the URL level and only relevant from the reverse proxy’s perspective. And the overwrite* values take care of making sure Nextcloud has all it needs to pass along the appropriate URLs externally.

1 Like