Nextcloud protection via Let's Encrpyt behind a reverse proxy?

Hello Nextcloudcomunity,
i’m new here and only recently started working on Linux, servers, nextcloud and more.
But I am very curious and want to host my own cloud. I’ve been working on that for many days and weeks now, but I’m having some problems that make me delete my LXCs again and again and have to start from scratch.
I would like to run my Nextcloud on an LEMP stack (i.e. with Nginx) behind a reverse proxy (also with Nginx).

But I seem to have a problem of understanding, because it just does not want to work. So first my simple question:
If I have Let’s Encrypt with certbot on my reverse proxy (LXC 1), i.e. the communication up to my reverse proxy in my local network is SSL encrypted, how important is it to set this up on my Nextcloud LXC (2) as well? Or can the communication between Reverse Proxy and Nextcloud be unencrypted ? What are the advantages and disadvantages ?

Otherwise I obviously have some problem to set up the nginx configuration of the virtual host correctly.

I’m pretty desperate because I don’t know where the error is.
It would be great if you could help me.

Thanks a lot.
Greetings

Reverse proxies by nature can be

  1. SSL terminating
  2. SSL passthrough
  3. SSL terminating and re-encrypting to the backend

Most examples you’ll see deal with #1 where the reverse proxy terminates the SSL connection and then uses http to the backend. This technically is going to be the fastest way to server many users. The backend connection however will be unencrypted. (Just for reference – nginx calls the backend “upstream” so if googling for examples from the nginx documentation – use the word “upstream”). If you don’t trust your internal LAN – or there are potential weakness in your LAN then this might be a problem.

SSL terminating and re-encrypting can also be performed. Usually the backend certificates are self-signed, however there is no reason you couldn’t use LE certs for backend communication as well. Since you control the reverse proxy and the nextcloud server – if you really wanted to get fancy/smancy with the encryption you could use client SSL certificates as well as server-side SSL certificates (LE certs are exclusively server-side SSL certificates). Computationally having to decrypt/re-encrypt is more expensive and it’s likely slower if you are serving a lot of users. I’ve seen however some debate about this theory particularly if using CPUs (Intel’s) that have AES-NI built-in (which are most modern processors). I haven’t run any tests which would demonstrate the speed or computational cost of this setup, however if you are only going to serve a handful of users, you’ll likely not notice any difference. This setup could be used if you don’t really trust your LAN or if it’s required by law – for example dealing with HIPPA-compliant data or banking data.

To take things a step farther you could also setup a collabora docker or native installation that is also protected by SSL certificates – so it’s always possible to do a triple-SSL connected stream -->reverse proxy–>nextcloud—>collabora (or open office).

I’ve implemented both #1 and #3 in my setup and usually just do #3 for security purposes – it’s likely overkill but I just wanted to see if I could set everything up.

3 Likes

Hello, kevdog,
Thank you for your explanation.
Option #3 sounds like what I actually want to do. It sounds more straightforward and if I’m going for SSL encryption, I’d like to go all the way. Overkill or not.

The thing with the certificates is not quite clear to me yet.
To be a bit more concrete, I had the following setup:
First I secured my Nextcloud LXC with LE and created SSL certificates for my Nextcloud subdomain with certbot. This worked so far.
After that I tried to connect the reverse proxy, created again a SSL certificate for my Nextcloud subdomain with certbot and tried to forward the traffic to the internal IP address of my Nextcloud LXC. In this case I always got a redirection error when I tried to access the Nextcloud URL.
Can, must, should the certificates be identical on both the reverse proxy (LXC1) and on the Nextcloud (LXC2) and go to my subdomain both times ?

Those were my configs:
Sorry if the formatting of my Configs is not perfect :wink:

On nextcloud-LXC
/etc/nginx/sites-enabled/nextcloud

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

server {
    listen 80;
    listen [::]:80;
    server_name subdomain.domain.tld;
    # enforce https
    return 301 https://$server_name:443$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name subdomain.domain.tld;

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

    # Use Mozilla's guidelines for SSL/TLS settings
    # https://mozilla.github.io/server-side-tls/ssl-config-generator/
    # NOTE: some settings below might be redundant
    ssl_certificate /etc/letsencrypt/live/subdomain.domain.tld/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/subdomain.domain.tld/privkey.pem;

    # Add headers to serve security related headers
    # Before enabling Strict-Transport-Security headers please read into this
    # topic first.
    #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;
    #
    # 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-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 /var/www/nextcloud;

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

    # The following 2 rules are only needed for the user_webfinger app.
    # Uncomment it if you're planning to use this app.
    #rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
    #rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;

    # The following rule is only needed for the Social app.
   # Uncomment it if you're planning to use this app.
    #rewrite ^/.well-known/webfinger /public.php?service=webfinger last;

    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 ap>

    # Uncomment if your server is build with the ngx_pagespeed module
    # This module is currently not supported.
    #pagespeed off;

    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)(\/.*|)$;
        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;
        # Avoid sending the security headers twice
        fastcgi_param modHeadersAvailable true;
        # Enable pretty urls
        fastcgi_param front_controller_active true;
        fastcgi_pass php-handler;
        fastcgi_intercept_errors on;
        fastcgi_request_buffering off;
    }

    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;" always;
        #
        # 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-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;

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

And here is the config on my reverse proxy:

server {

  server_name subdomain.domain.tld;

  location / {
      proxy_pass https://192.168.1.120/; #internal IP address of the LXC where my Nextcloud runs
  }

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

}

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


  listen 80;
  listen [::]:80;

  server_name subdomain.domain.tld;
    return 404; # managed by Certbot


}
1 Like

Nobody but kevdog uses a reverse proxy and full SSL encryption and can help me with this problem ? :astonished: :cry:
I would never have thought that my problem was so unique and difficult. How did you solve it alternatively, so that such problems don’t arise at all ? :thinking:

Have you tried #1 as an interim solution to verify that you can get it working without SSL? I have the same setup (but with FAMP stacks) and it has worked without any trouble for years. I can’t help specifically since I don’t knwo nginx, but I have a general tip. It can be problematic to have two things that can fail as a novice. Try to get the simple working and then add complexity. So I would do the following if I where you

  1. Try to get #1 working, so you know that the reverse proxy part works
  2. Check your logs. There should be clues in the error logs for nginx, on either the proxy or nextcloud container. Set the log level to debug, try to load the site and look in the logs for clues what went wrong.

Hello, Kebba,
a good suggestion. I tried it once. But I don’t even have the possibility to get the IP address via http :cry:
Whenever I type http://IP-Adresse of my Nextcloud installation in my browser, it automatically changes it to https and I get the message “Connection failed”.
I still think your idea is good to start with the simple things, but obviously nothing works without SSL already here.
I’m getting desperate about all these problems :rage:

I can help you but not until next week. Traveling however I have a reverse re-encrypting proxy with SSL server certs for my setup. I don’t have access to my setup till then. Looking at setup quickly it doesn’t seem you have headers setup correctly. Take a look at this post as this was my starting point:

Hi kevdog,
nice to hear from you and thanks for your help. That would be great.
You seem to know a lot about the subject :sunglasses: :+1:
Parallel to your link I found a “solution” which is obviously not quite clean yet, but at least it works.
I have now encrypted both my Nextcloud LXC and my Reverse Proxy -LXC with my subdomain SSL, and I have concocted a reverse proxy configuration that runs on a warning, but otherwise works.

server {
    listen 80;
    listen [::]:80;
    server_name  subdomain.domain.tld;
    # enforce https
    return 301 https://$server_name:443$request_uri;
}

server {
        listen 443;
        listen [::]:443;

        server_name subdomain.domain.tld;

        ssl_certificate /etc/letsencrypt/live/subdomain.domain.tld/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/subdomain.domain.tld/privkey.pem;

ssl on;
ssl_session_cache builtin:1000 shared:SSL:10m;
ssl_protocols TLSv1 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/access.log;

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 https://subdomain.domain.tld;
proxy_read_timeout 90;
proxy_redirect https://internal-IP-Adress https://subdomain.domain.tld;

}

}

Outside of my network everything seems to work by calling my URI and the access via reverse proxy to my nextcloud works as well.
But if I call the same URI from my internal network, I can access my Nextcloud as well, but my internal IP address will be displayed.
I have also just configured fail2ban. If I logged in incorrectly in the past (before the reverse proxy setting), my external router IP was displayed as the banned IP address. Now the internal IP address of my end device. So -why- the internal access seems to work only internally. Very confusing the whole thing :thinking:
I also tried to add the headers from your link in my Reverse Proxy Config, but then I get a “500 Internal Server Error” message.

Thanks again in advance for your help :blush:

I’m not sure what you mean by this: “But if I call the same URI from my internal network, I can access my Nextcloud as well, but my internal IP address will be displayed”

Is this what you are doing:
external—>Rv proxy—>nextcloud

And also attempting this:
internal—>Rv proxy—>nextcloud

of
internal—>nextcloud?

Honestly I’ve never seen a statement like this: (I’m no expert)
proxy_redirect https://internal-IP-Adress https://subdomain.domain.tld;

Why not just:
proxy_redirect https://subdomain.domain.tld;

Where is the DNS server on your LAN?

Hi kevdog,
yes, that’s exactly what I meant. I would have expected that the way is the same (no matter if internal or external) when I call my subdomain.

If I change the proxy_redirect from the current config to your suggested one, I get the following error message during the syntax check:

nginx: [warn] the “ssl” directive is deprecated, use the “listen … ssl” directive instead in /etc/nginx/sites-enabled/subdomain.domain.tld.conf:23
nginx: [emerg] invalid parameter “https://subdomain.domain.tld” in /etc/nginx/sites-enabled/subdomain.domain.tld.conf:40
nginx: configuration file /etc/nginx/nginx.conf test failed

I usually get the warning message, but it still works. With the error I now get when I remove the ip-address from the config, it doesn’t work anymore.

About DNS: I use the DynDNS service of my domain operator. In addition, my router has the function that I can connect the DynDNS service of my domain operator directly with it.