Nextcloud behind NGINX Reverse Proxy Issues

Hello,

I am trying to get my Nextcloud to function behind an NGINX reverse proxy. The error I am receiving is ERR_TOO_MANY_REDIRECTS. I have read a few posts on this topic but nothing I have tried has been successful so far so I am reaching out in hopes someone here can point me in the right direction.

NGINX Proxy Config

server {
	server_name cloud.domain.com;

    access_log /var/log/nginx/cloud.access.log;
    error_log /var/log/nginx/cloud.error.log;

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/cloud.domain.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/cloud.domain.com/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

    add_header Strict-Transport-Security "max-age=31536000" always; # managed by Certbot

    ssl_trusted_certificate /etc/letsencrypt/live/cloud.domain.com/chain.pem; # managed by Certbot
    ssl_stapling on; # managed by Certbot
    ssl_stapling_verify on; # managed by Certbot

    client_max_body_size 0;
    underscores_in_headers on;

    location / {
	    proxy_headers_hash_max_size 512;
	    proxy_headers_hash_bucket_size 64;
	    proxy_set_header Host $host;
	    proxy_set_header X-Forwarded-Proto $scheme;
	    proxy_set_header X-Real-IP $remote_addr;
	    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

	    add_header Front-End-Https on;
	    proxy_pass http://192.168.50.10;
	}
}

server {
    if ($host = cloud.domain.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80;
	server_name cloud.domain.com;
}

Nextcloud config.php

<?php
$CONFIG = array (
  'instanceid' => 'id',
  'passwordsalt' => 'passSalt',
  'secret' => 'secret',
  'trusted_domains' => 
  array (
    0 => 'cloud.domain.com',
    1 => '192.168.50.10',
    2 => '192.168.50.24',     # Proxy IP
  ),
  'datadirectory' => '/usr/share/nginx/nextcloud-data',
  'dbtype' => 'mysql',
  'version' => '18.0.4.2',
  'dbname' => 'name',
  'dbhost' => 'host',
  'dbport' => '',
  'dbtableprefix' => 'oc_',
  'mysql.utf8mb4' => true,
  'dbuser' => 'user',
  'dbpassword' => 'password',
  'installed' => true,
  'maintenance' => false,
  'trusted_proxies' => ['192.168.50.24'],
  'overwrite.cli.url' => 'https://cloud.domain.com',
  'overwritehost' => 'cloud.domain.com',
);

Nextcloud Server NGINX Config

server {
    server_name cloud.domain.com;

    # Add headers to serve security related headers
    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";

    # Path to the root of your installation
    root /usr/share/nginx/nextcloud/;

    access_log /var/log/nginx/nextcloud.access;
    error_log /var/log/nginx/nextcloud.error;

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    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;
    }

    # set max upload size
    client_max_body_size 1024M;
    fastcgi_buffers 64 4K;

    # Disable gzip to avoid the removal of the ETag header
    gzip off;

    error_page 403 /core/templates/403.php;
    error_page 404 /core/templates/404.php;

    location / {
       rewrite ^ /index.php;
    }

    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/.+|core/templates/40[34])\.php(?:$|/) {
       include fastcgi_params;
       fastcgi_split_path_info ^(.+\.php)(/.*)$;
       try_files $fastcgi_script_name =404;
       fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
       fastcgi_param PATH_INFO $fastcgi_path_info;
       #Avoid sending the security headers twice
       fastcgi_param modHeadersAvailable true;
       fastcgi_param front_controller_active true;
       fastcgi_pass unix:/run/php-fpm/www.sock;
       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)$ {
        try_files $uri /index.php$uri$is_args$args;
        add_header Cache-Control "public, max-age=7200";
        # Add headers to serve security related headers (It is intended to
        # have those duplicated to the ones above)
        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;
        # Optional: Don't log access to assets
        access_log off;
   }

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

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/cloud.domain.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/cloud.domain.com/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


    add_header Strict-Transport-Security "max-age=31536000" always; # managed by Certbot

    ssl_trusted_certificate /etc/letsencrypt/live/cloud.domain.com/chain.pem; # managed by Certbot
    ssl_stapling on; # managed by Certbot
    ssl_stapling_verify on; # managed by Certbot

}
server {
    if ($host = cloud.domain.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot
    listen 80;
    listen [::]:80;
    server_name cloud.domain.com;
}
1 Like

Solved my own problem! Posting here in case anyone else might have similar issues!

The problem was caused by the config in the nginx proxy in the proxy_pass.

    location / {
            proxy_headers_hash_max_size 512;
            proxy_headers_hash_bucket_size 64;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            add_header Front-End-Https on;
            # whatever the IP of your cloud server is
            proxy_pass http://192.168.50.10;
        }

Note: proxy_pass http://192.168.50.10;. This was pointing to my nextcloud server on port 80. This is the HTTP server block in the nginx config on the nextcloud server:

server {
    if ($host = cloud.domain.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80;
    listen [::]:80;
    server_name cloud.domain.com;

Note: return 301 https://$host$request_uri;. This was causing the request to loop back to my proxy and causes the infinite redirect loop. The solution? Adding a simple “s” in my proxy_pass value in the proxy config.

location / {
            proxy_headers_hash_max_size 512;
            proxy_headers_hash_bucket_size 64;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            add_header Front-End-Https on;
            # whatever the IP of your cloud server is
            proxy_pass https://192.168.50.10;
        }

I think every single reverse proxy example I’ve looked at today had a value of “http” so I never even thought about it. Anyways, hope this saves someones else some time if they’ve overlooked this as well!

Thanks for sharing this.
Solved my issue instantaneously …

:star_struck: