The Issue – I'm getting a 404 – File not Found when trying to access the App (Including First Time)
When trying to access the App on a browser via nextcloud.example.com I'm getting this Error from the Console Log of the NextCloud FMP Docker Container:
172.19.0.5 - 04/Mar/2023:22:36:01 +0000 "GET /index.php" 404
And this error from the Console Log of the NGinX Docker Container:
172.69.67.108 - - [04/Mar/2023:22:36:01 +0000] "GET / HTTP/2.0" 404 36 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36" "xxx.my.ip.xxx"
The setup of the Docker Server Host and the Containers
-
Docker Host Server OS is Ubuntu 22.04 LTS
-
NGinX Docker Container, I am aware that a docker-compose.yml is best, but for now and until I get this working properly, I'm running the Container with:
docker run -p 80:80 -p 443:443 --name nginx \ --restart=unless-stopped \ -v certbot:/etc/letsencrypt \ -v nginx:/etc/nginx/conf.d \ -v www:/usr/share/nginx/html \ --network tools \ -d nginx
This is the only Container on the Docker Server Host with Ports Exposed and uses volume mounts to share:
- Certificates with a CertBot Container
- NGinX Conf Directory to set up a conf for each app
- HTML www root home to share with the NextCloud FPM Docker Container
- Network Tools
-
NextCloud FPM Docker Container, I'm running the Container with:
docker run --name nextcloud-fpm \ --restart=unless-stopped \ -v www:/var/www/html \ -e 'TRUSTED_PROXIES=nginx' \ -e 'POSTGRES_HOST=database.server.com' \ -e 'POSTGRES_USER=database_user' \ -e 'POSTGRES_PASSWORD=database_user_password' \ -e 'POSTGRES_DB=databse_name' \ --network tools \ -d nextcloud:fpm
This Container is run on the same Network Tools, to not have to expose the app's Ports to the Docker Host Server, and to make it easier when it's time to create the Container Stacks and NGinX conf file by using the Container Names instead of IPs for Proxy Reverses
- HTML www root home to share with the NGinX Docker Container
- Environmental Variables for Trusted Proxy (the NGinX Container
- Environmental Variables for PostgreSQL Database
- This Cotainer runs FPM on Port 9000
- Network Tools
-
NGinX conf Files, there are 2 of them, one default.conf to mainly not allow the Host Server to be reached by IP directly and port (Example xxx.xxx.xxx.xxx:80 or xxx.xxx.xxx.xxx:443):
server { # Removes Web Server Info server_tokens off; # HTTP listen 80 default_server; listen [::]:80 default_server; listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; ssl_reject_handshake on; # Identify Server without the name, hence just IP server_name _; # Return Empty Response return 444; }
Finally the nextcloud.conf, it was taken form the GitHub NextCloud FPM Official Repo:
upstream php-handler { server nextcloud-fpm:9000; # In here I set up the Stream with the NextCloud FPM Container name } server { listen 80; listen [::]:80; server_name nextcloud.example.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl; listen [::]:443 ssl; server_name nextcloud.example.com; ssl_certificate /etc/letsencrypt/live/nextcloud.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/nextcloud.example.com/privkey.pem; # HSTS settings # WARNING: Only add the preload option once you read about # the consequences in https://hstspreload.org/. This option # will add the domain to a hardcoded list that is shipped # in all major browsers and getting removed from this list # could take several months. #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always; # set max upload size client_max_body_size 512M; fastcgi_buffers 64 4K; # Enable gzip but do not remove ETag headers 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 application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/> # Pagespeed is not supported by Nextcloud, so if your server is built # with the `ngx_pagespeed` module, uncomment this line to disable it. #pagespeed off; # HTTP response headers borrowed from Nextcloud `.htaccess` 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 "none" always; add_header X-XSS-Protection "1; mode=block" always; # Remove X-Powered-By, which is an information leak fastcgi_hide_header X-Powered-By; # Path to the root of your installation root /usr/share/nginx/html/; # Since NGinX is the Web Server, I am pointing root to where the NextCloud Files are mounted inside the NGinX Container from the NextCloud FPM Container # Specify how to handle directories -- specifying `/index.php$request_uri` # here as the fallback means that Nginx always exhibits the desired behaviour # when a client requests a path that corresponds to a directory that exists # on the server. In particular, if that directory contains an index.php file, # that file is correctly served; if it doesn't, then the request is passed to # the front-end controller. This consistent behaviour means that we don't need # to specify custom rules for certain paths (e.g. images and other assets, # `/updater`, `/ocm-provider`, `/ocs-provider`), and thus # `try_files $uri $uri/ /index.php$request_uri` # always provides the desired behaviour. index index.php index.html /index.php$request_uri; # Rule borrowed from `.htaccess` to handle Microsoft DAV clients 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; } # Make a regex exception for `/.well-known` so that clients can still # access it despite the existence of the regex rule # `location ~ /(\.|autotest|...)` which would otherwise handle requests # for `/.well-known`. 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; } # Rules borrowed from `.htaccess` to hide certain paths from clients location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; } location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; } # Ensure this block, which passes PHP files to the PHP process, is above the blocks # which handle static assets (as seen below). If this block is not declared first, # then Nginx will encounter an infinite rewriting loop when it prepends `/index.php` # to the URI, resulting in a HTTP 500 error response. location ~ \.php(?:$|/) { # Required for legacy support rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-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; } location ~ \.(?:css|js|svg|gif)$ { try_files $uri /index.php$request_uri; expires 6M; # Cache-Control policy borrowed from `.htaccess` access_log off; # Optional: Don't log access to assets } 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; } }
What I have tried so far
-
My First instinct was to check the Logs, I shared them at the start of this post, and based on that I went to check if the Files were Present on both Containers, I did this in two different ways:
I have a Container of Portainer Running on the same Docker Host Server and also behind the sane NGinX Docker Container as Proxy Reverse, I attached a console and look for the files inside both Containers, also did
docker exec container_name -it bash
on both containers:NGinX Container, remember the mount was on
/usr/share/nginx/html
root@61564cff4e67:/# ls -lha /usr/share/nginx/html total 180K drwxrwxrwx 15 www-data www-data 4.0K Mar 4 13:24 . drwxr-xr-x 3 root root 4.0K Feb 9 04:36 .. -rw-r--r-- 1 www-data www-data 3.2K Mar 4 13:24 .htaccess -rw-r--r-- 1 www-data www-data 101 Mar 4 13:24 .user.ini drwxr-xr-x 47 www-data www-data 4.0K Mar 4 13:24 3rdparty -rw-r--r-- 1 www-data www-data 19K Mar 4 13:24 AUTHORS -rw-r--r-- 1 www-data www-data 34K Mar 4 13:24 COPYING drwxr-xr-x 50 www-data www-data 4.0K Mar 4 13:24 apps drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 config -rw-r--r-- 1 www-data www-data 4.0K Mar 4 13:24 console.php drwxr-xr-x 23 www-data www-data 4.0K Mar 4 13:24 core -rw-r--r-- 1 www-data www-data 6.2K Mar 4 13:24 cron.php drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 custom_apps drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 data drwxr-xr-x 2 www-data www-data 12K Mar 4 13:24 dist -rw-r--r-- 1 www-data www-data 156 Mar 4 13:24 index.html -rw-r--r-- 1 www-data www-data 3.4K Mar 4 13:24 index.php drwxr-xr-x 6 www-data www-data 4.0K Mar 4 13:24 lib -rwxr-xr-x 1 www-data www-data 283 Mar 4 13:24 occ drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 ocm-provider drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 ocs drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 ocs-provider -rw-r--r-- 1 www-data www-data 3.1K Mar 4 13:24 public.php -rw-r--r-- 1 www-data www-data 5.5K Mar 4 13:24 remote.php drwxr-xr-x 4 www-data www-data 4.0K Mar 4 13:24 resources -rw-r--r-- 1 www-data www-data 26 Mar 4 13:24 robots.txt -rw-r--r-- 1 www-data www-data 2.4K Mar 4 13:24 status.php drwxr-xr-x 3 www-data www-data 4.0K Mar 4 13:24 themes -rw-r--r-- 1 www-data www-data 383 Mar 4 13:24 version.php
NextCloud FPM Container, remember the mount was on
/var/www/html
root@938c95d0e1ae:/var/www/html# ls -lha total 180K drwxrwxrwx 15 www-data www-data 4.0K Mar 4 13:24 . drwxrwxr-x 1 www-data root 4.0K Feb 16 03:06 .. -rw-r--r-- 1 www-data www-data 3.2K Mar 4 13:24 .htaccess -rw-r--r-- 1 www-data www-data 101 Mar 4 13:24 .user.ini drwxr-xr-x 47 www-data www-data 4.0K Mar 4 13:24 3rdparty -rw-r--r-- 1 www-data www-data 19K Mar 4 13:24 AUTHORS -rw-r--r-- 1 www-data www-data 34K Mar 4 13:24 COPYING drwxr-xr-x 50 www-data www-data 4.0K Mar 4 13:24 apps drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 config -rw-r--r-- 1 www-data www-data 4.0K Mar 4 13:24 console.php drwxr-xr-x 23 www-data www-data 4.0K Mar 4 13:24 core -rw-r--r-- 1 www-data www-data 6.2K Mar 4 13:24 cron.php drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 custom_apps drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 data drwxr-xr-x 2 www-data www-data 12K Mar 4 13:24 dist -rw-r--r-- 1 www-data www-data 156 Mar 4 13:24 index.html -rw-r--r-- 1 www-data www-data 3.4K Mar 4 13:24 index.php drwxr-xr-x 6 www-data www-data 4.0K Mar 4 13:24 lib -rwxr-xr-x 1 www-data www-data 283 Mar 4 13:24 occ drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 ocm-provider drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 ocs drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 ocs-provider -rw-r--r-- 1 www-data www-data 3.1K Mar 4 13:24 public.php -rw-r--r-- 1 www-data www-data 5.5K Mar 4 13:24 remote.php drwxr-xr-x 4 www-data www-data 4.0K Mar 4 13:24 resources -rw-r--r-- 1 www-data www-data 26 Mar 4 13:24 robots.txt -rw-r--r-- 1 www-data www-data 2.4K Mar 4 13:24 status.php drwxr-xr-x 3 www-data www-data 4.0K Mar 4 13:24 themes -rw-r--r-- 1 www-data www-data 383 Mar 4 13:24 version.php
Docker Server Host Bind Mount Lastly to check the files o the Docker Host Server itself, as I do not only want to achieve sharing the root location for both Containers also have persistent data if I need to move the Container:
❯ ll total 180K drwxrwxrwx 15 www-data www-data 4.0K Mar 4 13:24 . drwxrwxr-x 10 Alejandro Alejandro 4.0K Mar 4 13:24 .. drwxr-xr-x 47 www-data www-data 4.0K Mar 4 13:24 3rdparty drwxr-xr-x 50 www-data www-data 4.0K Mar 4 13:24 apps -rw-r--r-- 1 www-data www-data 19K Mar 4 13:24 AUTHORS drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 config -rw-r--r-- 1 www-data www-data 4.0K Mar 4 13:24 console.php -rw-r--r-- 1 www-data www-data 34K Mar 4 13:24 COPYING drwxr-xr-x 23 www-data www-data 4.0K Mar 4 13:24 core -rw-r--r-- 1 www-data www-data 6.2K Mar 4 13:24 cron.php drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 custom_apps drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 data drwxr-xr-x 2 www-data www-data 12K Mar 4 13:24 dist -rw-r--r-- 1 www-data www-data 3.2K Mar 4 13:24 .htaccess -rw-r--r-- 1 www-data www-data 156 Mar 4 13:24 index.html -rw-r--r-- 1 www-data www-data 3.4K Mar 4 13:24 index.php drwxr-xr-x 6 www-data www-data 4.0K Mar 4 13:24 lib -rwxr-xr-x 1 www-data www-data 283 Mar 4 13:24 occ drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 ocm-provider drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 ocs drwxr-xr-x 2 www-data www-data 4.0K Mar 4 13:24 ocs-provider -rw-r--r-- 1 www-data www-data 3.1K Mar 4 13:24 public.php -rw-r--r-- 1 www-data www-data 5.5K Mar 4 13:24 remote.php drwxr-xr-x 4 www-data www-data 4.0K Mar 4 13:24 resources -rw-r--r-- 1 www-data www-data 26 Mar 4 13:24 robots.txt -rw-r--r-- 1 www-data www-data 2.4K Mar 4 13:24 status.php drwxr-xr-x 3 www-data www-data 4.0K Mar 4 13:24 themes -rw-r--r-- 1 www-data www-data 101 Mar 4 13:24 .user.ini -rw-r--r-- 1 www-data www-data 383 Mar 4 13:24 version.php root@myserver /mnt/Volume/data/www
I see no reason why permission wise it would not work and have confirmed that the files are present in all three instances as they should.
-
Check the NGinX conf file for NextCloud FPM nextcloud.conf, after a lot of consideration the only thing I think can be wrong since I'm reaching via the domain on a browser all the way to the NGniX Web Server Container, as I have 404 Not Found entries in its log and I'm also getting a response from the NextCloud Container instance itself a 404 Not Found as well, that the only problem I have is related to the root entry on the conf file:
NextCloud Log:
172.19.0.5 - 04/Mar/2023:22:36:01 +0000 "GET /index.php" 404
NGinX Log:
172.69.67.108 - - [04/Mar/2023:22:36:01 +0000] "GET / HTTP/2.0" 404 36 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36" "xxx.my.ip.xxx"
So I changed the root directive to be
/var/www/html
, and the behavior changes just a bit, I'm still getting a 404 Not Found entry but only on the NGinX Web Server Container logs, the NextCloud Container is silent, so I'm not reaching it with this change:NextCloud Log (empty no entry at all):
NGinX Log:
2023/03/05 00:13:24 [error] 473#473: *4152 "/var/www/html/index.php" is not found (2: No such file or directory), client: 172.69.65.73, server: nextcloud.example.com, request: "GET / HTTP/2.0", host: "nextcloud.example.com" 172.69.65.73 - - [05/Mar/2023:00:13:24 +0000] "GET / HTTP/2.0" 404 174 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36" "xxx.my.ip.xxx"
There's something different in the way the Web Server behaves, I notice that after the root directive change, I see the actual domain between quotes on the log
"nextcloud.example.com"
when before was just a dash"-"
. -
Next thing was to just disable default.conf NGinX conf file, as is the only place I can think of the
"-"
, same results as above and I repeated on both scenarios 1 and 2 I have already tried, it is a"_"
on the server directive of the default.conf too so was a shot in the dark. -
I have tried many other settings with the conf but most of them end up being
Too Many Redirections
,Bad Certificate
, or just plain502
errors.
My Determination of What the Problem and Where I'm stuck
It seems I'm very close to my desired result, as I am reaching both Containers from browsers with the desired domain, and SSL is resolving fine as well, however, the server name doesn't seem to be captured by the NGinX conf file, and then not passed correctly to NextCloud, but I don't know what exactly to change.
Since I think I narrowed it down to what I think is the specific issue maybe someone can help me review the NGinX conf file nextcloud.conf and point out what is set wrong.
Thanks, for even reading this long post.
Best Answer
I figured it out, so I will give the detailed info to help people with the same problem already and for people that might run into this in the future, considering how many questions and issues I found about this without answers.
The Issue was the FPM root location in the FastCGI Parameters of the NGinX conf file
So I was on the right track, and the problem was tied to the root directive.
After much reading I stumbled upon this post:
How to correctly link php-fpm and Nginx Docker containers?
And suddenly it became clear I had two different services reading 2 different paths, and one was feeding the root path of the other one where it didn't exist.
Remember I had mounted the www volume to be shared between the 2 containers?
www:/usr/share/nginx/html
www:/var/html/www
The idea behind this was for the two containers to share the filesystem of the NextCloud app.
What I didn't realize is that in this deployment, NGinX's purpose is to serve the static files, like CSS, HTML, JPG, JS, etc., while routing PHP calls to the NextCloud FPM container with the FastCGI Parameters.
When the root directive in the NGinX conf file is declared, it gets set as the variable
$document_root
.So when setting the root to the path on the NGinX Container
/usr/share/nginx/html
this works to serve the static files without issues, but then, when a PHP call is made it's fed to the php-hanlder in the NextCloud FPM Container, since that location doesn't exist inside the second container, it can't find the PHP file.To fix this I searched for the block with the FastCGI Parameters and searched for the
$document_root
variable, found it, and looks like this:Note how the line that says
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
is forming the Script Path with the root already declared, so when processingnextcloud.example.com
it tries to readindex.php
and this is the resulting call to the NextCloud FPM container:/usr/share/nginx/html/index.php
The correct call would have been:
/var/www/html/index.php
So I created a new variable named
$fpm_root
and set it to/var/www/html/
, then changed the line that saysfastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
tofastcgi_param SCRIPT_FILENAME $fpm_root$fastcgi_script_name;
Now whenever it gets a call for static content it will resolve NGinX's Container path and when it gets a call for PHP content the NextCloud FPM's Container path.
Here's how the whole section looks now:
It works now and passes all the internal NextCloud health and security checks once I added the line:
Conclusion
And I hope this helps anyone searching for this issue, the problem was that I had two different paths for the services processing both types of content, static and PHP, and only one of them was properly set on the NGinX conf file.
You can have two different (or more) root locations for a dockerized FPM deployment, and this could probably do to work or deploy static content into two or more different apps using the same FPM container to handle PHP calls for NextCloud, WordPress, PHPPGAdmin, Laravel or whatever you are working on.