Confused with multiple levels of TLS certs that want to generate everywhere

1. Output of caddy version:

v2.6.2 h1:wKoFIxpmOJLGl3QXoo6PNbYvGW4xLEgo32GPBEjWL8o=

2. How I run Caddy:

  • Domain nameservers set to Cloudflare
  • Running Caddy v2 as a reverse proxy in front of multiple other web servers. This is done to achieve a “single ip” for working with clients.
  • Web servers configured with Runcloud.io that handle websites. Runcloud has ability to manage TLS certs and provision Let’s Encrypt certs.

a. System environment:

  • Bare metal running on Proxmox
  • Caddy Ubuntu 22 running on proxmox (IP: 67.215.9.132)
  • Runcloud web server / Ubuntu 22 running on proxmox (IP: 67.215.10.172)

b. Command:

Caddy running on system daemon via API mode

d. My complete Caddy config:

{
    "admin": {
        "disabled": false,
        "enforce_origin": false,
        "listen": "0.0.0.0:2019",
        "origins": [
            "localhost:2021",
            "localhost:2019",
            "134.41.73.117"
        ]
    },
    "apps": {
        "http": {
            "servers": {
                "w3stage": {
                    "automatic_https": {
                        "disable_certificates": true
                    },
                    "routes": [
                        {
                            "handle": [
                                {
                                    "handler": "reverse_proxy",
                                    "upstreams": [
                                        {
                                            "dial": "67.215.10.172"
                                        }
                                    ]
                                }
                            ],
                            "match": [
                                {
                                    "host": [
                                        "w3stage.com",
                                        "www.w3stage.com"
                                    ]
                                }
                            ]
                        }
                    ]
                }
            }
        }
    },
    "storage": {
        "module": "file_system",
        "root": "/caddy"
    }
}

3. The problem I’m having:

I can’t get w3stage.com to load up with an SSL cert at this time.

The Caddy server has entries for w3stage.com and should be pushing traffic to the Runcloud server.
In the Runcloud server, I have an application provisioned for w3stage.com, all nginx host files created (for both domain and www.)

I need to answer “where should I be generating my TLS certs at?” in two separate scenarios:

a) With Cloudflare Proxy enabled

In this scenario, Cloudflare provides a TLS cert. I think I would want both Caddy and Runcloud to serve the domain using Cloudflare’s cert and not try to do anything else.

b) With Cloudflare proxy disabled (or, a non-cloudflare nameserver)

In this scenario, I think I would want Runcloud to generate the cert and have Caddy forward all traffic for the domain to Runcloud, and let that server sort things out.

Am I wrong on these assumptions?

For each of these scenarios, how must I alter my Caddy config?

5. What I already tried:

  • With Cloudflare proxy enabled, i’ve seen cipher mismatching
  • With Cloudflare proxy disabled, i’ve seen redirect loops

When I try to generate a Let’s Encrypt cert on the Runcloud server (via http authorization), it fails because i believe Caddy picks up the requests.

The origin server should always generate the certificate to ensure security:

Caddy should generate the cert. It can still proxy your site (“orange cloud”) if you want it to. But Caddy should generate the cert, so that Cloudflare fetches are trusted to the origin.

Caddy should generate the cert.


So you probably want to make sure that your ports 80 and 443 are open to the public and remove disable_certificates: true – note that Cloudflare will terminate TLS, so the TLS-ALPN challenge won’t work, but the HTTP challenge should (on port 80).

For your proxy, make sure to dial a socket, not just an IP address – you’ll get connection errors.

Thanks matt. I finally got this working the way I wanted and posted an entry to the Wiki for anyone else wanting to do this in the future.

Update. I was able to achieve success when using simple HTML content, but when I started to implement a Wordpress installation, I am now back to a 301 redirect loop.

Interestingly, if I confgure Wordpress for http://afkgear.com, there is no redirect loop, but instead creates another issue where some plugins refuse to work without https://.

If I configure Wordpress for https://afkgear.com, we get a 301 redirect loop.

If I understand things correctly, this is the conversation my browser has:

me: please send afkgear.com
cloudflare: ok, i am sending you to caddy over SSL
caddy: hi. here is my local SSL. Now I am proxying you to ubuntu on port 80
ubuntu: hi. here is wordpress on afkgear.com.
wordpress: I want https. redirecting you to port 443 >>
caddy: hi. here is my local SSL. Now I am proxying you to ubuntu on port 80....
....

Latest configuration:

Domain: afkgear.com

DNS: cloudflare proxy enabled (orange cloud), with DNS pointing at Caddy (65.217.9.132)

Caddyfile:

{
	debug
	storage file_system /caddy
	email support@siteservice.net
	# log default {
	# 	output stdout
	# 	format json
	# 	include http.log.access admin.api
	# }
	admin 0.0.0.0:2019 {
		origins localhost:2019 134.41.73.117
	}
}

afkgear.com, www.afkgear.com {
	tls internal
	reverse_proxy 67.215.10.172
}

On the Ubuntu server at 67.215.10.172 (managed with runcloud.io), nginx is also supplying a Lets Encrypt cert.

I know it’s outside the scope of Caddy, obviously, but I am including all of the nginx .conf details below just in case it might be relevant.

server {
    server_name  afkgear.com;

    listen       80;
    listen       [::]:80;

    include /etc/nginx-rc/conf.d/afkgear.d/main.conf;
}
# HTTP_BLOCK

server {
    server_name www.afkgear.com;

    listen 80;
    listen [::]:80;

    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    brotli on;
    brotli_static off;
    brotli_min_length 100;
    brotli_buffers 16 8k;
    brotli_comp_level 5;
    brotli_types *;

    # SSL config
    ssl_certificate             /etc/nginx-rc/conf.d/afkgear.ssl.d/afkgear.com.crt;
    ssl_certificate_key         /etc/nginx-rc/conf.d/afkgear.ssl.d/afkgear.com.key;
    ssl_prefer_server_ciphers   on;
    ssl_session_timeout         5m;
    ssl_protocols               TLSv1.2 TLSv1.3;
    ssl_stapling                on;
    ssl_stapling_verify         on;
    resolver                    8.8.8.8 8.8.4.4 valid=86400s;
    resolver_timeout            5s;
    ssl_ciphers                 "EECDH+AESGCM:EDH+AESGCM";
    ssl_ecdh_curve              secp384r1;
    ssl_session_cache           shared:SSL:10m;
    ssl_session_tickets         off;
    ssl_dhparam                 /etc/nginx-rc/dhparam.pem;

    # HSTS DISABLED

    return 301 $scheme://afkgear.com$request_uri;
}
# REDIRECT_BLOCK_HTTP_AND_HTTPS

server {
    server_name  afkgear.com;

    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    brotli on;
    brotli_static off;
    brotli_min_length 100;
    brotli_buffers 16 8k;
    brotli_comp_level 5;
    brotli_types *;

    # SSL config
    ssl_certificate             /etc/nginx-rc/conf.d/afkgear.ssl.d/afkgear.com.crt;
    ssl_certificate_key         /etc/nginx-rc/conf.d/afkgear.ssl.d/afkgear.com.key;
    ssl_prefer_server_ciphers   on;
    ssl_session_timeout         5m;
    ssl_protocols               TLSv1.2 TLSv1.3;
    ssl_stapling                on;
    ssl_stapling_verify         on;
    resolver                    8.8.8.8 8.8.4.4 valid=86400s;
    resolver_timeout            5s;
    ssl_ciphers                 "EECDH+AESGCM:EDH+AESGCM";
    ssl_ecdh_curve              secp384r1;
    ssl_session_cache           shared:SSL:10m;
    ssl_session_tickets         off;
    ssl_dhparam                 /etc/nginx-rc/dhparam.pem;

    # HSTS DISABLED

    include /etc/nginx-rc/conf.d/afkgear.d/main.conf;
}
# HTTPS_BLOCK

headers.conf

# Header option for security purpose
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";

include /etc/nginx-rc/extra.d/afkgear.headers.*.conf;

main.conf

server_tokens           off;
error_log               /home/runcloud/logs/nginx/afkgear_error.log;
access_log              /home/runcloud/logs/nginx/afkgear_access.log main buffer=16k;
access_log              /var/log/nginx-rc/afkgear_traffic.log traffic;

client_max_body_size    256m;

include /etc/nginx-rc/conf.d/afkgear.d/headers.conf;

root /home/runcloud/webapps/afkgear;
index index.php index.html index.htm;

include /etc/nginx-rc/extra.d/afkgear.location.main-before.*.conf;

location ~ /.well-known/acme-challenge {
    auth_basic off;
    allow all;
    log_not_found off;
    root  /opt/RunCloud/letsencrypt;
}

location / {
    include /etc/nginx-rc/extra.d/afkgear.location.root.*.conf;
    include /etc/nginx-rc/conf.d/afkgear.d/proxy.conf;
}

location ~ /\. {
    deny all;
    access_log off;
    log_not_found off;
}

location = /favicon.ico {
    log_not_found off;
    include /etc/nginx-rc/extra.d/afkgear.location.favicon.*.conf;
}

location ~* .(ico|css|gif|jpe?g|png|gz|zip|flv|rar|wmv|avi|css|js|swf|png|htc|mpeg|mpg|txt|otf|ttf|eot|woff|woff2|svg|webp|avif|wasm)$ {
    expires     1M;
    include /etc/nginx-rc/extra.d/afkgear.location.static.*.conf;
    try_files $uri @proxy;
}

location ~ .(html)$ {
    expires     24h;
    include /etc/nginx-rc/extra.d/afkgear.location.html.*.conf;
    try_files $uri @proxy;
}

include /etc/nginx-rc/extra.d/afkgear.location.main.*.conf;

location @proxy {
    include /etc/nginx-rc/conf.d/afkgear.d/proxy.conf;
}

proxy.conf

proxy_send_timeout         60;
proxy_read_timeout         60;
proxy_buffer_size          128k;
proxy_buffers              4 256k;
proxy_busy_buffers_size    256k;
proxy_temp_file_write_size 256k;
proxy_connect_timeout       30s;

proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Server-Addr $server_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

include /etc/nginx-rc/extra.d/afkgear.location.proxy.*.conf;