Apache won't serve Nextcloud when using SSL. Is it the cert, Apache config, or VPN?

Hello, All. This is my first post.

The Setup

I’m in the process of setting up my first server hosted on my home network. My goal is to have a Nextcloud server that my family and I can access from anywhere using a normal web URL. I want to host the server on my local network for all the usual reasons; however, I don’t want to give out my network’s public IP. To avoid putting my public IP out directly, I’ve set up a VPS with a WireGuard connection between the VPS and my Nextcloud server. I’ve set the DNS record to point to the IP of the VPS instance and configured the VPS to forward everything on ports 80 and 443 over the WG tunnel to the Nextcloud server. The Nextcloud server is running Fedora 44 Server Edition.

The Problem

I can access the server just fine over port 80 when I change my Apache config to not redirect to https or when I access it directly via local IP on port 80. But, when accessing over port 443, it gives ERR_SSL_PROTOCOL_ERROR in Chrome and SSL_ERROR_RX_RECORD_TOO_LONG in Firefox; if I access it via local IP on 443, it serves up the default Apache welcome page with the default unsigned cert.

What I’ve Tried

I’ve tried revoking and re-issuing certs (I’m using acme.sh and letsencrypt), changing my Apache config, and double-checking my firewall settings. By all accounts, the connections seem to be going through fine on port 80 and 443 both. Something seems to be up with how Apache is handling the cert or SSL connection, but I can’t figure out where it’s going wrong. Of note, I originally had my domain proxied by Cloudflare and I tried issuing a cert using the Cloudflare API; issuing the cert seemed successful, but Cloudflare never showed that the connection between their proxy servers and my server was encrypted – only the connection between clients and Cloudflare was encrypted.

Anyone have an idea where the problem is? I’m out of ideas.

The Configs

WireGuard connection(s)

Cloud VPS:

[Interface]
Address = 10.168.24.1/32
ListenPort = 24837
PrivateKey = <Private key>

PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;iptables -A FORWARD -o %i -j ACCEPT; ip6tables -A FORWARD -i %i -j ACCEPT; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;ip6tables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 10.168.24.2:80; iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j DNAT --to-destination 10.168.24.2:80; iptables -t nat -A POSTROUTING -o wg0 -p tcp -d 10.168.24.2 --dport 80 -j SNAT --to-source 10.168.24.1; iptables -t nat -A POSTROUTING -o wg0 -p tcp -d 10.168.24.2 --dport 443 -j SNAT --to-source 10.168.24.1; iptables -A FORWARD -i eth0 -o wg0 -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT; iptables -A FORWARD -i eth0 -o wg0 -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT; iptables -A FORWARD -i eth0 -o wg0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT; iptables -A FORWARD -i wg0 -o eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE;iptables -D FORWARD -o %i -j ACCEPT; ip6tables -D FORWARD -i %i -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE;ip6tables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 10.168.24.2:80; iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 443 -j DNAT --to-destination 10.168.24.2:80; iptables -t nat -D POSTROUTING -o wg0 -p tcp -d 10.168.24.2 --dport 80 -j SNAT --to-source 10.168.24.1; iptables -t nat -D POSTROUTING -o wg0 -p tcp -d 10.168.24.2 --dport 443 -j SNAT --to-source 10.168.24.1; iptables -D FORWARD -i eth0 -o wg0 -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT; iptables -D FORWARD -i eth0 -o wg0 -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT; iptables -D FORWARD -i eth0 -o wg0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT; iptables -D FORWARD -i wg0 -o eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

[Peer]
PublicKey = <Public key>
AllowedIPs = 10.168.24.2/32

Nextcloud server:

[Interface]
Address = 10.168.24.2/32
ListenPort = 24837
PrivateKey = <Private key>
 
[Peer]
Endpoint = <IP here>:24837
PublicKey = <Public key>
AllowedIPs = 10.168.24.2/32
PersistentKeepalive = 25

Apache config
<VirtualHost *:80>
  ServerName nextcloud.example.com
  Redirect permanent / https://nextcloud.example.com/
</VirtualHost>


<VirtualHost *:443>
  ServerName nextcloud.example.com
  SSLEngine on
  DocumentRoot /var/www/nextcloud

  # SSL Configuration
  SSLCertificateFile "/etc/ssl/nextcloud.example.com/nextcloud.example.com.cer"
  SSLCertificateKeyFile "/etc/ssl/nextcloud.example.com/nextcloud.example.com.key"
  SSLCertificateChainFile "/etc/ssl/nextcloud.example.com/ca.crt"
  SSLCACertificatePath "/etc/letsencrypt/nextcloud.example.com/"
  SSLCACertificateFile "/etc/letsencrypt/nextcloud.example.com/fullchain.cer"

  <Directory /var/www/nextcloud>
    Require all granted
    AllowOverride All
    Options FollowSymLinks MultiViews

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

  Protocols h2 http/1.1
</VirtualHost>


LoadModule ssl_module "/etc/httpd/modules/mod_ssl.so"
LoadModule rewrite_module "/etc/httpd/modules/mod_rewrite.so"
LoadModule headers_module "/etc/httpd/modules/mod_headers.so"
LoadModule env_module "/etc/httpd/modules/mod_env.so"
LoadModule dir_module "/etc/httpd/modules/mod_dir.so"
LoadModule mime_module "/etc/httpd/modules/mod_mime.so"

Cert issuing/config
nextcloud:~$ acme.sh --issue -k 4096 -d nextcloud.example.com -w /var/www/nextcloud
[Wed Aug  5 04:47:07 PM CDT 2026] Using CA: https://acme-v02.api.letsencrypt.org/directory
[Wed Aug  5 04:47:07 PM CDT 2026] Creating domain key           
[Wed Aug  5 04:47:08 PM CDT 2026] The domain key is here: /etc/letsencrypt/nextcloud.example.com/nextcloud.example.com.key                                                                      
[Wed Aug  5 04:47:08 PM CDT 2026] Single domain='nextcloud.example.com'
[Wed Aug  5 04:47:09 PM CDT 2026] Getting webroot for domain='nextcloud.example.com'
[Wed Aug  5 04:47:09 PM CDT 2026] nextcloud.example.com is already verified, skipping http-01.  
[Wed Aug  5 04:47:09 PM CDT 2026] Verification finished, beginning signing.                     
[Wed Aug  5 04:47:09 PM CDT 2026] Let's finalize the order.                                                                                                                                     
[Wed Aug  5 04:47:09 PM CDT 2026] Le_OrderFinalize='https://acme-v02.api.letsencrypt.org/acme/finalize/'
[Wed Aug  5 04:47:11 PM CDT 2026] Downloading cert.                                                                                                                                             
[Wed Aug  5 04:47:11 PM CDT 2026] Le_LinkCert='https://acme-v02.api.letsencrypt.org/acme/cert/'
[Wed Aug  5 04:47:11 PM CDT 2026] Cert success.
-----BEGIN CERTIFICATE-----                                                                                                                                                              [0/245]
<Cert here>
-----END CERTIFICATE-----                                                                       
[Wed Aug  5 04:47:11 PM CDT 2026] Your cert is in: /etc/letsencrypt/nextcloud.example.com/nextcloud.example.com.cer       
[Wed Aug  5 04:47:11 PM CDT 2026] Your cert key is in: /etc/letsencrypt/nextcloud.example.com/nextcloud.example.com.key  
[Wed Aug  5 04:47:11 PM CDT 2026] The intermediate CA cert is in: /etc/letsencrypt/nextcloud.example.com/ca.cer                                                                          
[Wed Aug  5 04:47:11 PM CDT 2026] And the full-chain cert is in: /etc/letsencrypt/nextcloud.example.com/fullchain.cer       
[Wed Aug  5 04:47:12 PM CDT 2026] ARI suggestedWindow: 2026-10-03T23:32:44Z to 2026-10-05T18:43:34Z                                                                                      
[Wed Aug  5 04:47:12 PM CDT 2026] Next renewal time picked from ARI window: 2026-10-03T23:55:46Z


nextcloud:~$ acme.sh --install-cert -d nextcloud.example.com --cert-file /etc/ssl/nextcloud.example.com/nextcloud.example.com.cer --key-file /etc/ssl/nextcloud.example.com/nextcloud.example.com.key --fullchain-file /etc/ssl/nextcloud.example.com/ca.crt

First, let me say welcome! :slight_smile:

Let’s see if we can solve your issue. I’m not an Apache expert, but with the error and taking a look at your configs, I think you’ve just got a misconfiguration in your Apache config.

The SSLCACertifcatePath and SSLCACertificateFile options, are from what I can tell, usually used for mutual TLS, which it doesn’t sound like you’re trying to implement. Alternatively, they might be used to build your certificate chain of trust if you don’t already have a full chain file. That said, normally you would use one or the other, not both. In your case, I don’t think you need either.

Try this first, it seems to be the modern config. The fullchain.cer file you were provided should contain your cert, as well as all of the CA certs in your trust chain, so you won’t need the SSLCertificateChainFile option.

# SSL Configuration
  SSLCertificateFile "/etc/letsencrypt/nextcloud.example.com/fullchain.cer"
  SSLCertificateKeyFile "/etc/ssl/nextcloud.example.com/nextcloud.example.com.key"

Alternatively, what you have may also work if you drop the SSLCACertifcatePath and SSLCACertificateFile options. It sounds like this configuration might be deprecated though.

# SSL Configuration
  SSLCertificateFile "/etc/ssl/nextcloud.example.com/nextcloud.example.com.cer"
  SSLCertificateKeyFile "/etc/ssl/nextcloud.example.com/nextcloud.example.com.key"
  SSLCertificateChainFile "/etc/ssl/nextcloud.example.com/ca.crt"

References:

Thanks, for the welcome!

I’ve just tried your suggested config changes, but they unfortunately didn’t seem to have any effect. The browser still returns the same error, and accessing the server with the local IP on port 443 still serves the default Apache webpage and self-signed cert.

The configuration language for apache is awful, unless you have a need for apache specifically I’d try something else. Caddy is a good modern choice. Caddy has built in ACME support and SSL “just works”, there’s no need to deal with these pains in 2026.

If sticking to apache I’d suggest turning on as much verbose logging as you can and tracing how apache is dealing with SSL. Check the permissions on your certificates (and parent directories), apache may be having trouble reading them.

Thanks for the recommendation. I’d never heard of Caddy. It almost looks too easy.

I would really like to understand where my Apache config is going wrong. I might turn on verbose logging if I can get up the gumption to put more effort into Apache. I might just use Caddy.

https://configurator.tlsref.org is a good reference for modern TLS configurations, though I don’t really see anything comparing your posted config vs the tlsref.org one if you made the changes jackTL suggests. Which implies to me an environment error, pointing back to permissions. idk about acme.sh but certbot generally creates its keyfiles so that they are only readable by root.

Given apache is just giving you a default page, perhaps it’s not even loading your config file. I know apache under debian has a contrived sites_available vs sites_enabled system, where only config files in sites_enabled are loaded.

A reverse proxy, like Caddy, can be an easy and valid way to get HTTPS working with Nextcloud. I do it with my own setup using HAProxy.

That said, I also agree that there’s a likely permissions issue or other misconfiguration still happening. Looking at your virtualhost config file, if it’s the only enabled site, any request that doesn’t include a host name that matches a configured ServerName should still default to the first virtualhost entry in the file.

Can you provide the output of the apachectl -S command? That should help determine if any other configs are active and if things are configured correctly.

apachectl -S

Apache also has tools to test out the current config to help find issues.

apachectl configtest

If that doesn’t work, you might need to use apache2ctl:

apache2ctl -S
apache2ctl configtest

Additionally, it might also be helpful to output the contents of the sites-available and sites enabled directories, along with permissions, if you’re running Debian/Ubuntu. We should also validate the SSL certs are where they are configured and have the correct permissions as well.

ls -la /etc/apache2/sites-available/
ls -la /etc/apache2/sites-enabled/
ls -la /etc/ssl/nextcloud.example.com/
ls -la /etc/letsencrypt/nextcloud.example.com/

It seems you’re obfuscating your domain name, so make sure you do the same with the outputs.

Can you also confirm that /etc/httpd/ exists as a path on your system? That’s the typical RHEL/CentOS path, not Debian/Ubuntu. That said, I don’t see which OS you’re running, so maybe it is correct.

If you are running Debian/Ubuntu however, then your LoadModule entries in your Apache config are probably not loading correctly. More likely the path to the modules is /usr/lib/apache2/modules/. Instead of loading them via the config file, you could also just load them permanently using a2enmod mod_ssl for example, and disable with a2dismod mod_ssl

I found this tutorial on setting up SSL for Apache and it seems fairly complete. Consider reading through it. For example, it discusses using RewriteRule instead of Redirect, which is honestly probably something you want.

1 Like

honest question, have you seen the Apache logs? do they tell you anything?

I’ll try to send more info as I have time. I notice those errors about not being able to determine the servers fully qualified domain name; when I was in the admin center of the Nextcloud app, it was saying something about not being able to resolve it’s own name. I’m guessing the two issues are related, and possibly related to the SSL issue.

Here’s the output of httpd -S:

[Thu Aug 13 19:40:47.204061 2026] [so:warn] [pid 189125:tid 189125] AH01574: module ssl_module is already loaded, skipping
[Thu Aug 13 19:40:47.204101 2026] [so:warn] [pid 189125:tid 189125] AH01574: module rewrite_module is already loaded, skipping
[Thu Aug 13 19:40:47.204109 2026] [so:warn] [pid 189125:tid 189125] AH01574: module headers_module is already loaded, skipping
[Thu Aug 13 19:40:47.204117 2026] [so:warn] [pid 189125:tid 189125] AH01574: module env_module is already loaded, skipping
[Thu Aug 13 19:40:47.204123 2026] [so:warn] [pid 189125:tid 189125] AH01574: module dir_module is already loaded, skipping
[Thu Aug 13 19:40:47.204131 2026] [so:warn] [pid 189125:tid 189125] AH01574: module mime_module is already loaded, skipping
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using fe80::xxxx:xxxx:xxxx:xxxx%eno1. Set the 'ServerName' directive globally to suppress this message
VirtualHost configuration:
*:443                  is a NameVirtualHost
         default server fe80::xxxx:xxxx:xxxx:xxxx%eno1 (/etc/httpd/conf.d/ssl.conf:56)
         port 443 namevhost fe80::xxxx:xxxx:xxxx:xxxx%eno1 (/etc/httpd/conf.d/ssl.conf:56)
         port 443 namevhost nextcloud.example.com (/etc/httpd/sites-enabled/nextcloud.conf:1)
*:80                   nextcloud.example.com (/etc/httpd/sites-enabled/nextcloud.conf:28)
ServerRoot: "/etc/httpd"
Main DocumentRoot: "/var/www/html"
Main ErrorLog: "/etc/httpd/logs/error_log"
Mutex ssl-stapling: using_defaults
Mutex proxy: using_defaults
Mutex authn-socache: using_defaults
Mutex ssl-cache: using_defaults
Mutex default: dir="/etc/httpd/run/" mechanism=default 
Mutex cache-socache: using_defaults
Mutex authdigest-opaque: using_defaults
Mutex watchdog-callback: using_defaults
Mutex proxy-balancer-shm: using_defaults
Mutex rewrite-map: using_defaults
Mutex ssl-stapling-refresh: using_defaults
Mutex authdigest-client: using_defaults
Mutex dav_fs-lockdb: using_defaults
Mutex lua-ivm-shm: using_defaults
PidFile: "/etc/httpd/run/httpd.pid"
Define: DUMP_VHOSTS
Define: DUMP_RUN_CFG
User: name="apache" id=48 not_used
Group: name="apache" id=48 not_used

Here’s the output of apachectl configtest:

sudo apachectl configtest
[Thu Aug 13 19:53:01.206729 2026] [so:warn] [pid 189879:tid 189879] AH01574: module ssl_module is already loaded, skipping
[Thu Aug 13 19:53:01.206765 2026] [so:warn] [pid 189879:tid 189879] AH01574: module rewrite_module is already loaded, skipping
[Thu Aug 13 19:53:01.206770 2026] [so:warn] [pid 189879:tid 189879] AH01574: module headers_module is already loaded, skipping
[Thu Aug 13 19:53:01.206773 2026] [so:warn] [pid 189879:tid 189879] AH01574: module env_module is already loaded, skipping
[Thu Aug 13 19:53:01.206777 2026] [so:warn] [pid 189879:tid 189879] AH01574: module dir_module is already loaded, skipping
[Thu Aug 13 19:53:01.206781 2026] [so:warn] [pid 189879:tid 189879] AH01574: module mime_module is already loaded, skipping
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using fe80::xxx:xxxx:xxx:xxxx%eno1. Set the 'ServerName' directive globally to suppress this message
Syntax OK

I think you are overcomplicating your setup and running into weird problems because of it.

Why not use the Cloudflare Proxy option to hide your WAN IP and use a local reverse proxy?
This will simplify your setup and give you options to host more services if you want in the future.

Start with the 443 forwarding. I noticed this in your config: 443 → 10.168.24.2:80 That is probably the whole problem. You are sending HTTPS traffic to Apache HTTP port, so the browser gets an SSL error. 443 should go to 443 instead: 443 → 10.168.24.2:443 After changing that, restart Apache and check that it’s actually listening on 443. The Lets Encrypt certificate looks fine from the output you posted, so I wouldn’t bother reissuing it yet. If it still fails after fixing the forwarding, then look at the Apache SSL vhost next.

3 Likes

Good grief, well that certainly wouldn’t help my situation. I’ve changed that iptables rule on my VPS proxy to redirect traffic correctly: 443 → 10.168.24.2:443.

I finally found the solution!

I don’t have IPv6 set up (very dinosaur of me), so I had to explicitly tell the Apache VirtualHost to listen only on Port 443 over IPv4: <VirtualHost 0.0.0.0:443> [...] </VirtualHost>.

Now it works like a charm. I reckon I’ll see about setting up that IPv6. It is the future after all.

This has been very educational. Thanks all for the help!

2 Likes

Glad you got it sorted! I’d been meaning to sit down and look into this some more but life has been busy.

1 Like

Happy you got it solved. I would suggest trying ‘syncin’ instead. I found it is much faster and less glitchy. Basically the same software but nextcloud runs on php and i think syncin uses javascript.

I think I’ll stick with Nextcloud for now. I’m looking for something that has chat, calendar, and other Google-like features.
Thanks for the recommendation!