NGINX Reverse Proxy for NextCloud (Apache)

Support intro

Public DNS = web.domain.com

Fortigate FW with DNAT in place for ports 80 and 443 to waf.domain.com

System = waf.domain.com (listens at web.domain.com)
Operating system and version = Ubuntu 20.04
Has been setup to renew Let’s Encrypt certs automatically

System = nextcloud.domain.com
Nextcloud version = 20.0.1
Operating system and version = Ubuntu 20.04
Apache version = Apache 2.4.41
PHP version = 7.4
NextCloud root at /var/www/nextcloud
Nextcloud available at nextcloud.domain.com

All VMs run on ESXi 7.0.1.

(Now, please read this very carefully). I don’t want to come across ungrateful or aggressive, but I am posting a detailed intro as I want to avoid all the typical unneeded questioning I see on these forums. I would appreciate not receiving questions that are asked for the sake of questioning what I am doing.

I am new to NextCloud and to web administration in general and am implementing all of this for advanced home use. I have a single static public IP (with public DNS) and I want to use an NGINX proxy to:

  1. Learn NGINX
  2. Learn ModSecurity
  3. Likely now learn Apache and NextCoud :slight_smile:
  4. Publishing external websites asides from NextCloud

Originally I had installed the Hansson VM, however after moving /mnt/ncdata from the default ZFS to NFS (which runs on FreeNAS ZFS anyway), the upgrade script failed as it was looking for the original ZFS partition. I also wanted to get an in depth understanding of the solution anyway so I stopped using that solution.

I now have an operational NextCloud setup - by that I mean NextCloud ‘Security and Setup Warnings’ shows all checks passed :slight_smile: . Originally I deployed on 20.0.0 and upgraded to 20.0.1 once I knew it was all working as expected. I kept the Hansson VM around and used it as a reference for resolving any issues - this included getting Redis installed, using Fastcgi(?), HTTP vs HTTPs etc.

Essentially what I am trying to achieve:

  1. Public access to NextCloud at web.domain.com/nc
  2. Public access to a password manager at web.domain.com/pw
  3. Whatever else I may want to publish - maybe a website of my own :slight_smile:

What I can’t figure out:

  1. Should NGINX simply redirect to Apache - if so how?
  2. Should NGINX become the web front end for NextCloud completely - if so how?
  1. I am also equally unsure how I managed to remove the original /nexctloud path (location?) and replace with /. Too many posts suggested it is configured in sites-available/*.conf or in .htaccess or in config/config.php.

Further to this, when I had /nextcloud in place, I seemed to have my proxy config working. I know this because if I accessed nextcloud.domain.com/nextcloud and ran the config checks I was getting the Strict Transport Protocol warnings. However, when I was proxying using web.domain.com/nextcloud the warnings disappeared as it was part of the NGINX SSL options. I have though replaced the original pages in sites-available since then though - thought it may be relevant.

I am happy to send through whatever configs anybody wants to see, but first was interested in understanding if there was anybody who might be able to assist.

Thanks in advance.

P.S. Also please don’t give me the ‘search the forums first’ type responses. As you can imagine having now got all this operational you can probably see for yourself that I spent hours and hours checking logs and searching the forums (BTW NextCloud’s website is painfully slow) but there are so many people asking for assistance that I ended up breaking more configs than I fixed and had to revert snapshots to get back where I started - hence why I am now asking.

1 Like

is nextcloud.domain.com pointing to the internal ip address of your apache server?
it should point to your external ip address.

nginx should act as a reverse proxy. the following example might not work, but i hope you get the idea:

server {
    listen 80;
    server_name pw.domain.com;
    return 301 https://$host$request_uri;
}

server {

    listen 443 ssl;
    server_name pw.domain.com;

    ssl_certificate           /etc/letsencrypt/live/pw.domain.com/fullchain.pem;
    ssl_certificate_key       /etc/letsencrypt/live/pw.domain.com/privkey.pem;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    ssl_session_cache  builtin:1000  shared:SSL:10m;
    ssl_protocols  TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
    ssl_prefer_server_ciphers on;

    access_log            /var/log/nginx/pw.domain.com.access.log;

    client_max_body_size 2048M;

    location / {

      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_pass          http://<iternal-ip_pw>;
      proxy_read_timeout  300;

      proxy_redirect      http://<iternal-ip_pw> https://pw.domain.com;
    }
}

server {
    listen 80;
    server_name nextcloud.domain.com;
    return 301 https://$host$request_uri;
}

server {

    listen 443 ssl;
    server_name nextcloud.domain.com;

    ssl_certificate           /etc/letsencrypt/live/nextcloud.domain.com/fullchain.pem;
    ssl_certificate_key       /etc/letsencrypt/live/nextcloud.domain.com/privkey.pem;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    ssl_session_cache  builtin:1000  shared:SSL:10m;
    ssl_protocols  TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
    ssl_prefer_server_ciphers on;

    access_log            /var/log/nginx/nextcloud.domain.com.access.log;

    client_max_body_size 2048M;

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

    location / {

      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_pass          http://<iternal-ip_nextcloud>;
      proxy_read_timeout  300;

      proxy_redirect      http://<iternal-ip_nextcloud> https://nextcloud.domain.com;
    }
}

ok?

Thanks @Reiner_Nippes. I am trying to use DNS as opposed to hardcoded IPs, so essentially:
web.domain.com = public IP (external) > Firewall DNAT > waf.domain.com (internal) For the purposes of getting this up and running, the external public access is irrelevant. If the web.domain.com (internal) setup works, it should work exactly the same externally.

web.domain.com (internal) > CNAME > waf.domain.com (internal)
waf.domain.com (NGINX proxy) listens on 80 and 443 and can forward to any internal system i.e. nextcloud.domain.com (internal) and password.domain.com (internal) - from my understanding, this is why I will need the /nc (nextcloud) and /pw (password server) locations as redirects or rewrites to their relevant servers - because the server name and ports are exactly the same.

My original config looked very similar to what you proposed - though I used most of yours instead to test, though still no good.

I am happy to focus on nextcloud for now - ignoring other sites. I want to get the redirection of / working initially, then we can work on switching this to /nc later. I have posted my configs below:

/etc/nginx/sites-available/nextcloud.conf
### HTTP
server {
  listen 80;
  server_name web.domain.com;
  location = / {
  return 301 $https://$server_name$request_uri;
  }
}

### HTTPS
server {
  listen 443 ssl http2;
  server_name web.domain.com;
  include /etc/nginx/snippets/ssl-params.conf;

### HTTPS Location
  location = / {
  proxy_pass     https://nextcloud.domain.com;
  proxy_redirect https://nextcloud.domain.com https://$server_name;
  }

  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;
  }
  client_max_body_size 2048M;

### Proxy Settings
  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;
  }

### DNS
  resolver 10.83.10.10 10.83.10.20 valid=30s;

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

/etc/nginx/snippets/ssl-params.conf

  # SSL Certificate Location
  ssl_certificate /etc/letsencrypt/live/domain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/domain.com/privkey.pem;
  ssl_trusted_certificate /etc/letsencrypt/live/domain.com/cert.pem;

  # HTTP Options
  ssl_session_cache shared:SSL:50m;
  ssl_session_timeout 1d;
  ssl_session_tickets off;
  server_tokens off;

  # intermediate configuration
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
  ssl_prefer_server_ciphers on;

  # HSTS (ngx_http_headers_module is required) (63072000 seconds)
  add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;

  # OCSP stapling
  ssl_stapling on;
  ssl_stapling_verify on;

  ## Modsecurity Configuration
  #modsecurity on;
  #modsecurity_rules_file /etc/nginx/modsec/main.conf;```

/var/www/nextcloud/config/config.php
$CONFIG = array (
  'instanceid' => 'some_text',
  'passwordsalt' => 'some_text',
  'secret' => 'some_text',
  'trusted_domains' =>
  array (
    0 => '10.83.10.95',
    1 => 'nextcloud.domain.com',
    2 => 'web.domain.com',
  ),
  'trusted_proxies' =>
  array (
    0 => '10.83.10.40',
    1 => 'waf.domain.com',
  ),
  'datadirectory' => '/mnt/ncdata',
  'dbtype' => 'mysql',
  'version' => '20.0.1.1',
  'overwrite.cli.url' => 'https://nextcloud.domain.com',
  'htaccess.RewriteBase' => '/',
  'dbname' => 'nextcloud',
  'dbhost' => 'localhost',
  'dbport' => '',
  'dbtableprefix' => 'oc_',
  'mysql.utf8mb4' => true,
  'dbuser' => 'admin',
  'dbpassword' => 'some_text',
  'installed' => true,
  'updater.secret' => 'some_text',
  'login_form_autocomplete' => false,
  'memcache.local' => '\\OC\\Memcache\\APCu',
  'filelocking.enabled' => true,
  'memcache.distributed' => '\\OC\\Memcache\\Redis',
  'memcache.locking' => '\\OC\\Memcache\\Redis',
  'redis' =>
  array (
    'host' => '/var/run/redis/redis-server.sock',
    'port' => 0,
    'password' => 'some_text',
  ),
  'maintenance' => false,
  'theme' => '',
  'loglevel' => 2,
  'mail_from_address' => 'nextcloud',
  'mail_smtpmode' => 'smtp',
  'mail_sendmailmode' => 'smtp',
  'mail_domain' => 'domain.com',
  'mail_smtphost' => 'mail.domain.com',
  'mail_smtpport' => '25',
  'logtimezone' => 'some_text',
);```

I have noticed in the nextcloud.error.log the following message which leads me to believe NGINX is trying to find local websites which obviously don’t exist: 2020/11/03 00:55:59 [error] 1324#1324: *6 open() "/usr/share/nginx/html/login" failed (2: No such file or directory), client: 10.83.2.23, server: web.domain.com, request: "GET /login HTTP/1.1", host: "web.domain.com"

BTW, something to call out is that my Let’s Encrypt cert is a wildcard

you don’t need this. web.domain.com, nextcloud.domain.com and password.domain.com all point to the external ip. with a server_name xxx.domain.com; in the according nginx config file you will route the traffic to the different internal web server.

yes. but

or

and of course you use fqdn’s instead of ip adresses in the proxy_pass and proxy_redirect statement. you may use nextcloud.internal.domain.com.

btw: please use correct markdown for your config files. three “```” before and after to quote sample config files.

Thanks, I had read similar articles like this one: https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms

There is a section contained showing using location = /blah and location = / which would mean it had to be an exact match. This is what I was hoping to by adding /nc or /pw to the end of web.domain.com. I will follow your advice here, especially if you believe it is not possible.

At this stage however, there is no external config, everything is private. But for clarity I will use:

To this end I have also changed the web.domain.com in the nextcloud.conf to nc.domain.com. Initially as I had not updated the config.php to match and I was getting the ‘Access through untrusted domain’. Interestingly it was shown in plain text, the HTML formatting was not as expected.

I then updated the config.php file and restarted apache. When I then accessed nc.domain.com I see that /login is appended (nc.domain.com/login) as expected, but NGINX throws a ‘404 not found’

P.S. Thanks for highlighting the recommendation for the configs. Can you further explain how I apply that? I did find it strange initially that much of the #(commenting) was stripped. I tested it by editing the post but it didn’t seem to do anything.

Hi, I have this working now. If somebody can show me the correct way to upload my configs as @Reiner_Nippes pointed out, I will share.

this:
grafik

will be printed like this:

# your config here
server { ... }

if you don’t use the ticks “```” a # is displayed as a heading.

heading

it’s called markdown.

@Reiner_Nippes, thanks very much for all your help.

For anybody else, below are my configs in case they help anybody:

nginx nextcloud.conf
server {
  listen 80;
  server_name nc.domain.com;
  location  / {
  return 301 $https://$server_name$request_uri;
  }
}

# HTTPS
server {
  listen 443 ssl http2;
  server_name nc.domain.com;
  include /etc/nginx/snippets/ssl-params.conf;

# HTTPS Location
  location  / {
  proxy_pass     https://nextcloud.domain.com;
  proxy_redirect https://nextcloud.domain.com https://$server_name;
  }

  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;
  }
  client_max_body_size 2048M;

# Proxy Settings
  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;
  }
# DNS
  resolver dns-server1 dns-server2 valid=30s;

# Logging
  access_log /var/log/nginx/nextcloud.access.log;
  error_log /var/log/nginx/nextcloud.error.log;```
ssl-params.conf
### File is used for central SSL configuration ###

  # SSL Certificate Location
  ssl_certificate /etc/letsencrypt/live/domain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/domain.com/privkey.pem;
  ssl_trusted_certificate /etc/letsencrypt/live/domain.com/cert.pem;

  # HTTP Options
  ssl_session_cache shared:SSL:50m;
  ssl_session_timeout 1d;
  ssl_session_tickets off;
  server_tokens off;

  # intermediate configuration
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
  ssl_prefer_server_ciphers on;

  # HSTS (ngx_http_headers_module is required) (63072000 seconds)
  add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;

  # OCSP stapling
  ssl_stapling on;
  ssl_stapling_verify on;
apache nextcloud.conf
<Directory /var/www/nextcloud>
    Options Indexes FollowSymLinks
    AllowOverride All
    Require all granted
    Satisfy Any
    </Directory>

    <IfModule mod_dav.c>
    Dav off
    </IfModule>

    <Directory "/mnt/ncdata">
    # just in case if .htaccess gets disabled
    Require all denied
    </Directory>

    # The following lines prevent .htaccess and .htpasswd files from being
    # viewed by Web clients.
    <Files ".ht*">
    Require all denied
    </Files>

    # Disable HTTP TRACE method.
    TraceEnable off

    # Disable HTTP TRACK method.
    RewriteEngine On
    RewriteCond %{REQUEST_METHOD} ^TRACK
    RewriteRule .* - [R=405,L]

    SetEnv HOME /var/www/nextcloud
    SetEnv HTTP_HOME /var/www/nextcloud

    # Avoid "Sabre\DAV\Exception\BadRequest: expected filesize XXXX got XXXX"
    <IfModule mod_reqtimeout.c>
    RequestReadTimeout body=0
    </IfModule>

### LOCATION OF CERT FILES ###
#    SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
#    SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
     SSLCertificateFile /etc/ssl/certs/nextcloud.crt
     SSLCertificateKeyFile /etc/ssl/private/nextcloud.key
</VirtualHost>
nextcloud config.php
<?php
$CONFIG = array (
  'instanceid' => 'some_text_here',
  'passwordsalt' => 'some_text_here',
  'secret' => 'some_text_here',
  'trusted_domains' =>
  array (
    0 => '10.83.10.95',
    1 => 'nextcloud.domain.com',
    2 => 'nc.domain.com',
  ),
  'trusted_proxies' =>
  array (
    0 => '10.83.10.40',
    1 => 'waf.domain.com',
  ),
  'datadirectory' => '/mnt/ncdata',
  'dbtype' => 'mysql',
  'version' => '20.0.1.1',
  'overwrite.cli.url' => 'http://nextcloud.domain.com',
  'overwrite.protocol' => 'https',
  'overwrite.host' => 'nc.domain.com',
  'htaccess.RewriteBase' => '/',
  'dbname' => 'nextcloud',
  'dbhost' => 'localhost',
  'dbport' => '',
  'dbtableprefix' => 'oc_',
  'mysql.utf8mb4' => true,
  'dbuser' => 'admin',
  'dbpassword' => 'insert_text_here',
  'installed' => true,
  'login_form_autocomplete' => false,
  'memcache.local' => '\\OC\\Memcache\\APCu',
  'filelocking.enabled' => true,
  'memcache.distributed' => '\\OC\\Memcache\\Redis',
  'memcache.locking' => '\\OC\\Memcache\\Redis',
  'redis' =>
  array (
    'host' => '/var/run/redis/redis-server.sock',
    'port' => 0,
    'password' => 'insert_text_here',
  ),
  'maintenance' => false,
  'theme' => '',
  'loglevel' => 2,
  'mail_from_address' => 'nextcloud',
  'mail_smtpmode' => 'smtp',
  'mail_sendmailmode' => 'smtp',
  'mail_domain' => 'domain.com',
  'mail_smtphost' => 'mail.domain.com',
  'mail_smtpport' => '25',
  'logtimezone' => 'insert_timezone',

I should add, these configs got me an A+ on both the ssllabs ssl checker as well as the NextCloud Security Checker

Hope someone is still following this post. In the config.php example the first three line say: ‘some text here’. What does that mean? What do I put there?

Nothing. The first three lines should already be there. They are created automatically during installation and should not be changed.

Thanks for the reply.
I’ve never have put up a website, much less an https website with a reverse proxy, so things get a little confusing at times along the lines of whether the information is to be added by me or not…and what it is exactly that I’m adding. I’m good at following instructions if they’re straight forward…much less when I have to try to piece things together.
For example, the nextcloud.config file (above) has a trusted proxy array of 10.83.10.40 and waf.domain.com…and three addresses in other domain areas (nextcloud, nc, and waf) I’m figuring the 10.83.10.40 address is his Nginx box, but I’m not sure where the waf.domain.com comes from or what the others point to. It’d be extremely helpful, to knuckleheads like me, if this is indeed an A+ install, to first describe the parameters. To complicate things, my ddns supplied me with a subdomain (name.mywire.hop) so I’m trying to figure out how THAT figures in all this mix.
Again, I’m pretty good at following instruction so any clarifications would help.
Again thanks

I’m not an expert when it comes to nginx and reverse proxy setups, and I don’t know why he uses so many different subdomains. But you specifically mentioned those three lines and that’s what I was responding to.

As for the Nextcloud config.php, it should probably look something like this:

'overwritehost' => 'name.mywire.hop',
'overwriteprotocol' => 'https',
'overwritewebroot' => '/',
'overwrite.cli.url' => 'https://name.mywire.hop/',
'htaccess.RewriteBase' => '/',
'trusted_proxies' => ['IP_OF_THE_NGINX_PROXY'],

But there is no “one fits them all” solution. At the end it depends on your specic setup. Maybe it’s better to open a new post, where you describe how your specific setup looks like and what you already have tried…

Ah. Now that makes sense.
I thank you so much for the reply.