Problem configuring nginx reverse proxy for NextCloud

Nextcloud version (eg, 18.0.2): 19.0.0.12
Operating system and version (eg, Ubuntu 20.04): Raspbian GNU/Linux 10 (buster)
nginx version (eg, Apache 2.4.25): nginx 1.14.2-2
PHP version (eg, 7.1): 7.3.18-1

The issue you are facing:

  • Raspi 1: Nextcloud host (192.168.222.9)
  • Raspi 2: Reverse proxy (192.168.222.10)
  • I have Nextcloud successfully running on Raspi 1. Everything is fine when connecting from internal network to 192.168.222.9 on http.
  • Raspi 2 is configured as reverse proxy. External host name: my.domain.net.
  • On Raspi 2 there are some hosts defined: first listening on 80 redirecting to https on port 443. Second host on port 443 doing all the SSL stuff. One more host for letsencrypt and the main one on port 81 for the proxy_pass (details see below).
  • My problem is, that if I try to access my.domain.net via https from any browser, I’m getting a redirect to https://my.domain.net:81/login.
    This is not working, since the host on 81 is not doing anything with SSL, therefore the browser comes back with ERR_TUNNEL_CONNECTION_FAILED.
    If I call manually https://my.domain.net/login I can successfully login to Nextcloud.
    How do I get rid of the redirect to port 81? Any ideas?

Is this the first time you’ve seen this error? (Y/N): Y

The output of your config.php file in /path/to/nextcloud:

<?php
$CONFIG = array (
  'passwordsalt' => 'abc',
  'secret' => 'abc',
  'trusted_domains' =>
  array (
    0 => 'localhost',
    1 => '192.168.222.9',
    2 => '192.168.222.10',
    3 => 'HOSTNAME.local',
    5 => 'nextcloudpi.local',
    7 => 'nextcloudpi',
    8 => 'nextcloudpi.lan',
  ),
  'trusted_proxies' =>
  array (
    0 => '192.168.222.10',
  ),
  'datadirectory' => '/mnt/nextcloud_data/data',
  'dbtype' => 'mysql',
  'version' => '19.0.0.12',
  'overwrite.cli.url' => 'http://localhost',
  'dbname' => 'nextcloud',
  'dbhost' => 'localhost',
  'dbport' => '',
  'dbtableprefix' => 'oc_',
  'mysql.utf8mb4' => true,
  'dbuser' => 'ncadmin',
  'dbpassword' => 'abc',
  'installed' => true,
  'instanceid' => 'ocml45m5a3a5',
  'memcache.local' => '\\OC\\Memcache\\Redis',
  'memcache.locking' => '\\OC\\Memcache\\Redis',
  'logtimezone' => 'Europe/Berlin',
  'redis' =>
  array (
    'host' => '/var/run/redis/redis.sock',
    'port' => 0,
    'timeout' => 0.0,
    'password' => 'abc',
  ),
  'tempdirectory' => '/var/www/nextcloud/data/tmp',
  'mail_smtpmode' => 'sendmail',
  'mail_smtpauthtype' => 'LOGIN',
  'mail_from_address' => 'admin',
  'mail_domain' => 'ownyourbits.com',
  'preview_max_x' => '2048',
  'preview_max_y' => '2048',
  'jpeg_quality' => '60',
  'loglevel' => '2',
  'log_type' => 'file',
  'maintenance' => false,
  'updater.secret' => 'abc',
  'theme' => '',
);

Nginx host on Raspi 1 (Nextcloud host)

upstream php-handler {
        # server 127.0.0.1:9000;
        server unix:/var/run/php/php7.3-fpm.sock;
}

server {
        listen 80;
        server_name 192.168.222.9;

        root /var/www/nextcloud;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html index.php;

        add_header X-Content-Type-Options "nosniff" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header X-Robots-Tag "none" 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 Referrer-Policy "no-referrer" always;

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

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

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

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

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

        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\/.+|oc[ms]-provider\/.+)\.php(?:$|\/) {
                fastcgi_split_path_info ^(.+?\.php)(\/.*)$;

                try_files $fastcgi_script_name =404;
                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 php-handler;
                fastcgi_intercept_errors on;
                fastcgi_request_buffering off;
                #fastcgi_read_timeout 1200;
        }


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

        # Adding the cache control header for js, css and map files
        # Make sure it is BELOW the PHP block
        location ~ \.(?:css|js|woff2?|svg|gif|map)$ {
                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)
                # Before enabling Strict-Transport-Security headers please read into
                # this topic first.
                #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
                #
                # 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 Referrer-Policy "no-referrer" always;
                add_header X-Content-Type-Options "nosniff" always;
                add_header X-XSS-Protection "1; mode=block" always;
                add_header X-Robots-Tag "none" always;
                add_header X-Download-Options "noopen" always;
                add_header X-Frame-Options "SAMEORIGIN" always;
                add_header X-Permitted-Cross-Domain-Policies "none" always;

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

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

}

Nginx gateway host on Raspi 2 (Reverse proxy)

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    root /var/www;
    server_name my.domain.net;

    location ^~ /.well-known/acme-challenge {
        proxy_pass http://127.0.0.1:82;
        proxy_redirect off;
    }

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

    location / {
        # Enforce HTTPS
        return 301 https://$server_name$request_uri;
        return 301 https://$server_addr$request_uri;
    }
}

server {
    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;
    server_name _;
    root /var/www;

    # Configure SSL
    ssl on;
    # Certificates used
    ssl_certificate /etc/letsencrypt/live/my.domain.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/my.domain.net/privkey.pem;

    # Not using TLSv1 will break:
    #   Android <= 4.4.40
    #   IE <= 10
    #   IE mobile <=10
    # Removing TLSv1.1 breaks nothing else!
        # There are not many clients using TLSv1.3 so far, but this can be activated with nginx v1.13
    ssl_protocols TLSv1.2;

    # Using the recommended cipher suite from: https://wiki.mozilla.org/Security/Server_Side_TLS
    ssl_ciphers 'some-key';

    # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
    ssl_dhparam /etc/nginx/ssl/dhparams.pem;

    # Specifies a curve for ECDHE ciphers.
    # High security, but will not work with Chrome:
    #ssl_ecdh_curve secp521r1;
    # Works with Windows (Mobile), but not with Android (DavDroid):
    #ssl_ecdh_curve secp384r1;
    # Works with Android (DavDroid):
    ssl_ecdh_curve prime256v1;

    # Server should determine the ciphers, not the client
    ssl_prefer_server_ciphers on;

    # OCSP Stapling
    # fetch OCSP records from URL in ssl_certificate and cache them
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/my.domain.net/fullchain.pem;
resolver 192.168.222.1;

    # SSL session handling
    ssl_session_timeout 24h;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;

    # Add headers to serve security related headers
    # HSTS (ngx_http_headers_module is required)
    # In order to be recoginzed by SSL test, there must be an index.hmtl in the server's root
    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    # Usually this should be "DENY", but when hosting sites using frames, it has to be "SAMEORIGIN"
    add_header Referrer-Policy "same-origin" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header X-Robots-Tag none;
    add_header X-Download-Options noopen;
    add_header X-Permitted-Cross-Domain-Policies none;

    location ^~ / {
        client_max_body_size 10G;
        proxy_connect_timeout 3600;
        proxy_send_timeout 3600;
        proxy_read_timeout 3600;
        send_timeout 3600;
        proxy_buffering off;
        proxy_request_buffering off;
        proxy_max_temp_file_size 10240;
        proxy_set_header Host $host;
        proxy_redirect off;
        # Following 2 options are needed when using web sockets
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://127.0.0.1:81;
    }
}

Nginx reverse proxy host on Raspi 2 (Reverse proxy)

server {
   listen localhost:81;
   server_name my.domain.net;

   access_log /var/log/nginx/my.domain.net.access.log;
   error_log /var/log/nginx/my.domain.net.error.log;

   location / {
      proxy_pass http://192.168.222.9;

      proxy_http_version  1.1;
      # proxy_cache_bypass  $http_upgrade;

      # proxy_set_header Upgrade           $http_upgrade;
      proxy_set_header Connection        "";
      # # proxy_set_header Host              $host;
      proxy_set_header X-Real-IP         $remote_addr;
      proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
      # proxy_set_header X-Forwarded-Proto $scheme;
      # # proxy_set_header X-Forwarded-Host  $host;
      # proxy_set_header X-Forwarded-Port  $server_port;
      proxy_buffering off;
      proxy_request_buffering off;
      client_max_body_size 0;
      proxy_redirect off;
   }
}

See the overwrite parameters in the reverse proxy section of the documentation.

https://docs.nextcloud.com/server/18/admin_manual/configuration_server/reverse_proxy_configuration.html#overwrite-parameters

Thanks for your advice. I did read that, but it did not solve my problem. Do you have a specific hint here?

Your advice was correct: I added

'overwritehost' => '<myproxyhostname>',
'overwriteprotocol' => 'https',
'overwritewebroot' => '/',

to my config.php and now everything works fine. Thanks again for your advice. I do have no idea, what I did wrong when trying this before sending the first post … :frowning: