Changing SSL cert on Nextcloud Nginx server

When I installed a self-hosted nextcloud instance, I used a self-signed cert and this worked on cloud.localdomain. I used nginx as the web-server. This was a few years ago.

Then I gave access to it to some family members and I thought it would be nice if the browsers didn’t give them warnings about the certificates. So I bought a domain name and set up a reverse proxy to handle the Let’s Encrypt certificates automatically. For the reverse proxy, I used caddy2 as it was easier to configure. This worked great such that I could access cloud.myshinydomain.com with the correct LE cert.

The problem occurred when I set up a LE cert for pfSense. After doing that, I changed the domain name of pfSense to myshinydomain.com. Then I could no longer access cloud.localdomain from within the network. So I went and updated the domain name on the VM that serves Nextcloud to myshinydomain.com.

Since then however, when I try to access cloud.myshinydomain.com, it serves up the old self-signed certificate and not the Let’s Encrypt one from the reverse proxy. Chromium just doesn’t let me get in – as I also setup HSTS strict on the nginx server. But Firefox allows me to get into Nextcloud once I add an exception but the cert is still the self-signed one.

  1. Can I still use the certificate management at the reverse proxy/caddy2 so that I don’t have to worry about managing certs in 2 different places?
  2. If so, does it mean that I would have to stop listening to port 443 on the nginx server and only serve Nextcloud on HTTP – and let the reverse proxy handle HTTPS?

I tried to simply remove the ssl configuration from the Nginx server, but I got HTTP Error 502 when trying to access cloud.myshinydomain.com or even the IP address of the Nextcloud VM.

What do I need to change in order for cloud.myshinydomain.com to use the correct LE cert from the reverse proxy?

Thanks in advance.

The easiest way to do this in pfsense would be to use HA Proxy with LE https://youtu.be/gVOEdt-BHDY

Ok – I’m a little confused on your setup, but what you want to do should be possible

I’m assuming like a single WAN cable entering your pfsense box with a port forward of 443 to the reverse proxy.

In terms of the reverse proxy Caddy, it would really depend on how you set it up, however I believe for most of the examples I’ve seen with nextcloud, the examples usually terminate the SSL connection and then proxy the connection to the backend (nginx likes to call the backend upstream FYI), as unencrypted http. Its possible to re-encrypt at the reverse proxy, but then you need 2 different LE certs installed both at the reverse proxy and the second on the nginx webserver serving nginx.

Usually the purpose of the reverse proxy is if you have multiple services which operate on the same port (such as 443). The reverse proxy is able to forward to the appropriate service/VM/etc based on name resolution. If you are not running multiple services that need 443, then do you need the reverse proxy?

In your setup, you should have local DNS entries of myshinydomain.com -> which points to the IP address of pfSense, and cloud.myshinydomian.com -> point to the IP address of your reverse proxy (if using a reverse proxy) or IP address of the nextcloud box (if not using reverse proxy).

Single WAN cable coming in, yes, but I don’t have any port forwards. I am not trying to access this from outside the network. I have a VPN server for that if the need ever arises.

I am using the reverse proxy caddy2 for 2 reasons:

  1. Central place to manage the Let’s Encrypt certs with auto-renewal
  2. So that I don’t have to remember the various IP and ports for all the services that my Proxmox server serves.

I usually have separate containers serving a single service, so no two services share the same IP AND port numbers. And again, I am not trying to access this using my WAN address. From the outside, once I VPN in, I can still use the RFC1918 addresses for those services.

As for the DNS entries. The way I set it up is,

  1. I have “A records” set up for every service that needs a Let’s Encrypt cert in my DNS account (cloudflare) for my domain name and pointing to my public IP. That allows LE to confirm the ownership and assign a cert for the various services – bitwarden, nextcloud, syncthing etc.
  2. Then I have Host Overrides defined for the same hostnames – all with the myshinydomain.com as the domain – in the DNS Resolver in pfSense. They all point to the IP of the reverseproxy container.
  3. The reverse proxy (caddy configuration json file) then points to each of the service using the IP address:port number format

This may or may not be the optimal way to do this – as I am not a network engineer by any stretch. Everything I did, I did by reading up articles and wikis on the web when setting it up.

Hope that helps understanding how my setup is. Please feel free to suggest alternatives in case I am doing something inherently incorrect.

EDIT: pfsense and proxmox themselves are not using the reverseproxy for their LE certs since they have built in options to set up LE using the acme package/script

@inxsible

I have much the same setup, but am using nginx as my reverse proxy. I don’t think whether you use nginx or Caddy as the reverse proxy matters much – its a reverse proxy.

Rereading your original problem, I’m not exactly sure your description. I really try to limit the use of self-signed certs since I think when you start to mix self-signed with LE signed scripts things become a headache. Are you looking to terminate the TLS connection at the reverse proxy or do you need to have an encrypted connection all the way to the Nextcloud server? If you don’t need encryption to the backend - use one SSL cert installed within Caddy and proxy to the backend over HTTP. If you need an encrypted connection then install SSL Cert both on Caddy and also within the nginx webserver located on the nextcloud installation. (Unfortunately by doing this you kind of defeat one of your goals – now you have 2 SSL certs that will need renewal on 2 different instances). In both cases discussed above you’ll have to have the DNS override entry on pfsense point to either the domain name or IP of the reverse proxy.

The only other solution is just don’t use the reverse proxy for nextcloud and have pfsense point directly to nextcloud, not the reverse proxy. The connection will be encrypted all the way up to nextcloud and you’ll only have one certificate to manage, however this certificate will be on the nextcloud installation and not the reverse proxy.

In terms of certs for pfsense – on my network I gave pfsense an LE cert known as pfsense.domain.com. Nextcloud is known as nextcloud.domain.com. Domain.com simply represents my main webpage served through a separate nginx webserver on a differnent VM than nextcloud. All 3 of these certs are let’s encrypt certs.

When utilizing a reverse proxy I try to terminate most of the SSL connections at the reverse proxy – simply because this makes SSL certificate management easier. The exception to this rule for me is particularly with my Bitwarden_RS server where I want the encryption to carry through all the way to the docker bitwarden server. I have bw.domain.com LE cert installed on the reverse proxy, and a bw-backend.domain.com LE cert installed on the bitwarden host machine. I use the reverse proxy in this particular instance to decrypt/re-encrypt to the backend. I suppose it would have been possible just to pass the tls connection directly to the backend, however when I was learning how to use nginx I couldn’t get this particular configuation working so I just opted for a terminating/re-encrypting proxy in this instance.

Haven’t been ignoring @LTS_Tom’s post up there. I looked into HAProxy but that would mean a completely new setup from what I currently have and also learning something new. I will keep looking into it, but I was hoping what I have could be tweaked a bit to get it working…

Right. I used a self-signed cert for the Nextcloud instance quite some time back. But now that I have moved to LE for all other services, I was hoping to do the same with Nextcloud without having to re-install Nextcloud from scratch in a different VM.

As for terminating the connections, I am ok with using HTTP from the reverse-proxy to the individual services. I do that for all others as well including bitwarden_rs. I’d like to have 1 place to manage the LE certs. Any particular reason for having bitwarden encrypted between the reverse proxy and the backend server?

Back to the issue at hand regarding nextcloud – Should I just remove the include ssl.conf from the nginx.conf file. and then also remove the listen 443 directives from nextcloud.conf files? Will that make it a HTTP only backend? and then continue to have the reverse proxy set up SSL for cloud.myshinydomain.com ?

I think I tried that and as I mentioned in my first post – I got HTTP 502 errors. Maybe I’ll try that again.

Yes I would remove the ssl blocks and configure nginx/nextcloud to listen on port 80. Terminate the TLS at the reverse proxy for cloud.myshinydomain.com.

If you get stuck you might need to post your nginx.conf file or your virtual domain file for cloud.myshinydomain.com.

I tried it out but no joy.

Here’s my nginx.conf – I commented the inclusion of the ssl.conf which lists the self signed certs

user www-data;
worker_processes auto;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
use epoll;
}
http {
server_names_hash_bucket_size 64;
upstream php-handler{
server unix:/run/php/php7.3-fpm.sock;
}
set_real_ip_from 127.0.0.1;
set_real_ip_from 10.10.10.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
include /etc/nginx/mime.types;
include /etc/nginx/proxy.conf;
#include /etc/nginx/ssl.conf;
include /etc/nginx/header.conf;
include /etc/nginx/optimization.conf;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
sendfile on;
send_timeout 3600;
tcp_nopush on;
tcp_nodelay on;
open_file_cache max=500 inactive=10m;
open_file_cache_errors on;
keepalive_timeout 65;
reset_timedout_connection on;
server_tokens off;
resolver 10.10.10.1 valid=30s;
resolver_timeout 5s;
include /etc/nginx/conf.d/*.conf;
}

Here’s my nextcloud conf – I commented the 8 lines related to https

 server {
  server_name cloud;
  listen 80 default_server;
  listen [::]:80 default_server;
  location ^~ /.well-known/acme-challenge {
    proxy_pass http://127.0.0.1:81;
    proxy_set_header Host $host;
  }
#  location / {
#    return 301 https://$host$request_uri;
#  }
#}
#server {
#  server_name cloud;
#  listen 443 ssl http2 default_server;
#  listen [::]:443 ssl http2 default_server;
  root /var/www/nextcloud/;
  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;
  }
  #SOCIAL app enabled? Please uncomment the following row
  #rewrite ^/.well-known/webfinger /public.php?service=webfinger last;
  #WEBFINGER app enabled? Please uncomment the following two rows.
  #rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
  #rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
  client_max_body_size 10240M;
  location / {
    rewrite ^ /index.php;
  }
  location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ {
    deny all;
  }
  location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) {
    deny all;
  }
  location ^~ /apps/rainloop/app/data {
    deny all;
  }
  location ~ \.(?:flv|mp4|mov|m4a)$ {
    mp4;
    mp4_buffer_size 100M;
    mp4_max_buffer_size 1024M;
    fastcgi_split_path_info ^(.+?.php)(\/.*|)$;
    set $path_info $fastcgi_path_info;
    try_files $fastcgi_script_name =404;
    include fastcgi_params;
    include php_optimization.conf;
  }
  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;
    include php_optimization.conf;
  }
  location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) {
    try_files $uri/ =404;
    index index.php;
  }
  location ~ \.(?:css|js|woff2?|svg|gif|map|png|html|ttf|ico|jpg|jpeg)$ {
    try_files $uri /index.php$request_uri;
    access_log off;
    expires 360d;
  }
}

Here’s my relevant reverse proxy config (caddy.json) just for the nextcloud instance: I changed the port from 443 to 80 as I removed the HTTPS server on nginx.

{
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "transport": {
                            "protocol": "http",
                            "tls": {
                              "insecure_skip_verify": true
                            }
                          },
                          "upstreams": [
                            {
                              "dial": "10.10.10.200:80"
                            }
                      ]
                    }
                  ]
                }
              ]
            }
          ],
          "match": [
            {
              "host": [
                "cloud.myshinydomain.com"
              ]
            }
          ],
          "terminal": true
        }

And lastly as I mentioned before, I have Host Override defined for cloud myshinydomain pointing to my reverse proxy IP in pfSense DNS Resolver.

However, when I access the cloud.myshinydomain.com, I get

This page isn’t working
cloud.myshinydomain.com is currently unable to handle this request.
HTTP ERROR 502

When I try to access nextcloud using simply the IP address I get

This site can’t be reached
10.10.10.200 refused to connect.
Try:
Checking the connection
Checking the proxy and the firewall
ERR_CONNECTION_REFUSED

Do you see anything obvious that I might be doing wrong? Thanks.

Ok – you’re going through the typical not working things that are very frustrating that happen to me as well.

Focus on getting nextcloud to run over port 80 first accessible via the IP address and then worry about the reverse proxy. Just do it in steps. For reference I typically use an nginx config similar to what is posted here (straight out of the nextcloud documentation): https://docs.nextcloud.com/server/15/admin_manual/installation/nginx.html

You are going to have to remove the SSL/443 sections. I’m not sure if you have a firewall in place – if not great – if you do then you need to let port 80 through. You can check using your browser (but always make sure to clear the cache between attempts since sometimes the old corrupted page is kept), or use curl both on the machine running nginx/nextcloud and also from a LAN machine. With curl you might need to add the verbose option to see where things are getting stuck. In addition look at the nginx logs to see whats going on as well since the error message sometimes isn’t that helpful. Once getting nextcloud accessible via IP address, we’ll add the reverse proxy on top.

I ended up creating a new proxmox container and setting up Nextcloud in there served only via HTTP. Then I created the json config for caddy2 for reverse proxying the Nextcloud instance.

{ "handle": [ { "handler": "subroute", "routes": [ { "handle": [ { "handler": "reverse_proxy", "upstreams": [ { "dial": "10.10.10.230:80" } ] } ] } ] } ], "match": [ { "host": [ "newcloud.myshinydomain.com" ] } ], "terminal": true },

Now the browser shows the correct Lets Encrypt cert. I still need to redirect the caldav and carddav urls correctly as Nextcloud shows warnings for that.

Now comes the task of migrating the data from the old cloud to the newCloud and setting up the users again.

Thanks for your help