Mismatch between actual nginx http header configuration and what nextcloud (including scanner) thinks

Nextcloud version: 15.0.4 (but previous as well)
Operating system and version: debian stretch (openmediavault 4.x)
Apache or nginx version: nginx 1.10.3
PHP version: 7.2

Hello,
I have an installation of nextcloud on my personal home server, started with nextcloud 13 and now on the latest release. About three weeks ago I have changed the domain pointing to nextcloud (not the actual installation directory in the system) from xxx.yyy.zz/cloud to aaa.bbb.cc and in doing that I have edited the nginx configuration (following the nextcloud documentation). Since then, I’m getting weird http headers errors from the nextcloud security scan website and from nextcloud itself. In particular, the scanner says that some http security headers are not set and my nextcloud installation is particularly referring to the fact that the “X-Frame-Options” is not set to sameorigin. The problem is that all these http headers are configured (I think) in the right way.

This is my nginx configuration (XXX to replace sensitive data):

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    ssl_certificate     XXX;
    ssl_certificate_key XXX;
    server_name cloud.iacchi.casa;
    set $root_path "XXX";
    root $root_path;
    index index.php;
    set $socket "XXX";
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass $socket;
    }
    access_log XXX;
    error_log  XXX;
    large_client_header_buffers 4 8k;
    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
add_header Referrer-Policy no-referrer;
add_header X-Frame-Options "SAMEORIGIN";

# Remove X-Powered-By, which is an information leak
fastcgi_hide_header X-Powered-By;

location = /robots.txt {
  add_header  Content-Type  text/plain;
  return 200 "User-agent: *\nDisallow: /\n";
}

location = /.well-known/carddav {
  return 301 $scheme://$host/remote.php/dav;
}
location = /.well-known/caldav {
  return 301 $scheme://$host/remote.php/dav;
}

location ~ /\.well-known/acme-challenge { 
    allow all;
}

client_max_body_size 3072M;
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 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/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;

location / {
    rewrite ^ /index.php$request_uri;
}

location ~ ^\/(?:build|tests|config|lib|3rdparty|templates|data)\/ {
    deny all;
}
location ~ ^\/(?:\.|autotest|occ|issue|indie|db_|console) {
    deny all;
}

location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+)\.php(?:$|\/) {
    fastcgi_split_path_info ^(.+?\.php)(\/.*|)$;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_path_info;
    fastcgi_param HTTPS on;
    #Avoid sending the security headers twice
    fastcgi_param modHeadersAvailable true;
    fastcgi_param front_controller_active true;
    fastcgi_pass $socket;
    fastcgi_intercept_errors on;
    fastcgi_request_buffering off;
}

location ~ ^\/(?:updater|ocs-provider)(?:$|\/) {
    try_files $uri/ =404;
    index index.php;
}

# Adding the cache control header for js and css files
# Make sure it is BELOW the PHP block
location ~ \.(?:css|js|woff2?|svg|gif)$ {
    try_files $uri /index.php$request_uri;
    add_header Cache-Control "public, max-age=15778463";
    # Add headers to serve security related headers (It is intended to have those duplicated to the ones above)
    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Robots-Tag none;
    add_header X-Download-Options noopen;
    add_header X-Permitted-Cross-Domain-Policies none;
    add_header Referrer-Policy no-referrer;
    add_header X-Frame-Options "SAMEORIGIN";

    # Optional: Don't log access to assets
    access_log off;
}

location ~ \.(?:png|html|ttf|ico|jpg|jpeg)$ {
    try_files $uri /index.php$request_uri;
    # Optional: Don't log access to other assets
    access_log off;
}
}

And if I check my website (https://cloud.iacchi.casa) on a http header checker like this one: https://www.webconfs.com/http-header-check.php I can see the all http headers are correctly sent. Despite this, the nextcloud security scan website (any my installation itself) says that all these headers are not set: https://scan.nextcloud.com/results/a12e2dea-a1fb-4205-b838-1090c5ef7585

What is going on here?

I’d say, that’s the problem and root cause here :slight_smile:

Warnings
X-Frame-Options - There was a duplicate X-Frame-Options header.

Oh, and the explanation: Nextcloud sets this header in the PHP code already. Setting it in the nginx config as well, causes the header to be sent twice and scanner will then complain about that. I’m not sure if it even breaks the header handling for all clients, like browsers or if only scanners have an issue with that.

Thank you very much for your answer and the link to securityheaders.com
However, your answer is valid for the X-Frame-Options header, but it still doesn’t explain why the Nextcloud scan website says that all the other headers are missing (I assume the red X next to them means that they are not there or that there is something wrong with them), as they are clearly there in my nginx config and in the results of the check in securityheaders.com. Do you/someone else have any idea about this? I have taken the nginx configuration basically straight from the Nextcloud installation manual, at least for what concerns the http headers.

Just to add on it: I’ve tried to remove those four headers (X-Content-Type-Options, X-XSS-Protection, X-Download-Options, X-Permitted-Cross-Domain-Policies) from the nginx config and if I do that the Nextcloud scan website says that they’re there and put a green tick next to them. However, with the same configuration, the securityheaders.com website reports that those headers are not there anymore. So, is this a Nextcloud scan website bug or what (now I have put those headers there again)?

Well, I only mentioned this one header specifically, because that is the one the scanner complains about to be set twice. And in the past, a header set twice was already the root cause to mess up other/ all headers (of the same type) for other users before.
The thing is, that the nginx config from nextcloud contains all headers except
add_header X-Frame-Options "SAMEORIGIN";

And the reason is the one I told you.

So again:

  • remove or comment the header line add_header X-Frame-Options "SAMEORIGIN";
  • make sure to have the following headers in your nginx config:
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Robots-Tag none;
    add_header X-Download-Options noopen;
    add_header X-Permitted-Cross-Domain-Policies none;
  • check your site again

Sorry I wasn’t clear. I did that before replying. If you check, that header is not duplicated anymore, but the Nextcloud scan still complains about the others.

Okay, yes, that wasn’t clear :slight_smile:
Checking your config again, it doesn’t really look like the one from the NC guide.

This is the part I cannot find in the guide. Are you sure this is correct?
I believe the php handling should be covered by the config line farther down below.

Can you actually do that and put http and https configuration in one server block? I never saw that so far.

The command
nginx -t
returns “okay”, right?

nginx -t reports:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

so I guess everything is right on that side. For everything else: I have Nextcloud installed on debian stretch running openmediavault (it’s a program that integrates deeply into debian and creates an interface to manage a NAS, which is where the webserver is installed). I manage nginx through openmediavault’s web interface and both parts of the nginx configuration that don’t sound right to you are automatically created by openmediavault’s scripts when I create the nginx configuration to host Nextcloud. In that configuration file, the first line that I add manually is the first add_header call.
I may add that I don’t think those two blocks of code are the source of my problem. Before I changed the domain for Nextcloud, when I had it installed in a folder instead of a subdomain root, the configuration was the same (except in some parts of course instead of referring to “location ~ ^/” I was referring to “location ~ ^/cloud/” as explained in the Nextcloud docs) and it was still created through openmediavault’s web interface, so those two blocks of code were still there. However, when I was running the Nextcloud scan website back then, all the security headers that now have an X next to them had a green tick (except of course X-Frame-Options that could not be set because Nextcloud was not running on a domain root directory).