Best practices for running an HTTPS reverse proxy behind a Wireguard tunnel

1. The problem I’m having:

I want to run Caddy as an HTTPS reverse proxy for a site “hidden” behind a Wireguard VPN. The goal is that unless a client has successfully established a tunnel with the server and belongs to its private network, browsing https://hidden.example.com won’t work.

To do that I’m running Wireguard on the server, which has a fixed IP in the private 10.0.0.0/8 range. Caddy is bound to that IP and uses a self-signed certificate, because LetsEncrypt is understandably not able to issue a cert for a private IP.

To finish off, on the DNS side I’ve created an A record for hidden.example.com whose value has been set to 10.0.0.1.

While this works well enough on Firefox despite the security warning, on Chrome it doesn’t. Chrome simply refuses to proceed anyway to the site, and complains about the server using HSTS (which I know isn’t true).

I was wondering if there’s a less awkward way to serve HTTPS sites from a server on a private IP that I might be missing, hopefully without the security warnings.

2. Error messages and/or full log output:

No errors

3. Caddy version:

4. How I installed and ran Caddy:

Caddy’s official APT repository.

a. System environment:

Debian 12.

b. Command:

The default systemd unit that automatically picks up /etc/caddy/Caddyfile config.

systemctl start caddy.service

d. My complete Caddy config:

hidden.example.com {
        tls internal
        bind 10.0.0.1
        reverse_proxy http://127.0.0.1:8080
}

5. Links to relevant resources:

Regarding HSTS on Caddy: [Proposal] Automatic HSTS · Issue #4751 · caddyserver/caddy · GitHub

Certificates are usually bound to domain names, not IP addresses. You can still use Let’s Encrypt with Caddy behind NAT or even on a private network. If Caddy is on an isolated network you can request and renew certificates on another host and transfer the certs to Caddy. You’d need to configure Caddy to load the external certificates instead of managing them internally. tls (Caddyfile directive) — Caddy Documentation

Just serve HTTP since all possible users are known and used the encrypted wireguard tunnels. The self-signed certificate isn’t really bringing any added security in that scenario.

Also, don’t set the HSTS header as it instructs the browsers that this site should be viewed over HTTPS only, and prevent downgrading to plain HTTP.

I know. I have a real domain, the twist is that its DNS entry points to a private IP instead of a public one. That’s why I think there’s no way for the LetsEncrypt service to issue a valid certificate for that combination of domain and IP address.

This was the first thing I tried, but I found out that the web app I’m reverse proxying uses some browser features that are only allowed when the site is served over HTTPS, so TLS is a hard requirement for me despite Wireguard already encrypting the connection.

Also I’m not doing anything HSTS related (see configuration block), I was just calling BS on Chrome:

I double-checked anyway, the Strict-Transport-Security header is not being used.

That depends on how you renew it. Did you try DNS challenge instead of HTTP? Certbot and other tools can do that for you. User Guide — Certbot 2.11.0 documentation

This can sometimes be fixed by setting header_up in your Caddy reverse_proxy settings. Otherwise, you’d need to fix the HTTPS issue.

Thanks for the pointers, I didn’t know it would be possible to sign such a certificate.

I was able to get a properly signed certificate using Caddy’s built-in DNS challenge, though this required a custom Caddy binary with a DNS Challenge provider (depends on how you edit your DNS records, in my case I was lucky because I do it in CloudFlare and Caddy supports that).

New config:

hidden.example.com {
        tls {
                dns cloudflare {$CF_API_TOKEN}
        }
        bind 10.0.0.1
        reverse_proxy http://127.0.0.1:8080
}

Other resources that have been useful, for future reference:

DNS Challenge explained: Automatic HTTPS — Caddy Documentation
How to enable DNS provider modules: How to use DNS provider modules in Caddy 2
Module dns.providers.cloudflare documentation: Modules - Caddy Documentation
How to override Caddy’s systemd unit to define environment variables: Keep Caddy Running — Caddy Documentation

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.