Reverse Proxy with SSL

1. Caddy version (caddy version):

v2.1.1 h1:X9k1+ehZPYYrSqBvf/ocUgdLSRIuiNiMo7CvyGUQKeA=

2. How I run Caddy:

I downloaded the caddy binary from the caddy website, and can either run it manually, or using the systemd .service file below.

a. System environment:

CentOS Linux release 7.7.1908 (Core)

b. Command:

caddy run --config /path/to/caddy_conf/caddy.conf

c. Service/unit/compose file:

[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target

[Service]
User=nginx
Group=nginx
ExecStart=/path/to/caddy_bin/caddy run --environ --config /path/to/caddy_conf/caddy.conf
ExecReload=/path/to/caddy_bin/caddy reload --config /path/to/caddy_conf/caddy.conf
TimeoutStopSec=5s
LimitNOFILE=1048576
LimitNPROC=512
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_BIND_SERVICE
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target

d. My complete Caddyfile or JSON config:

{
    "apps": {
        "http": {
            "servers": {
                "my_server": {
                    "listen": [":60000"],
                    "routes": [
                        {
                            "handle": [{
                                "handler": "reverse_proxy",
                                "upstreams": [
                                    {
                                        "dial": "192.168.199.12:80"
                                    }
                                ]
                            }]
                        }
                    ]
                }
            }
        }
    }
}

3. The problem I’m having:

Right now, caddy works fine. It listens on port 60000, and sends connections to 192.168.199.12:80. That’s great for HTTP.

I have a certificate chain file, and a private key file on my server. All I want is to somehow enable SSL. For now, I do NOT want anything automatic. No LetsEncrypt, or anything. I just want it to use my existing SSL cert and key. Clients connecting to port 60000 should be presented with this certificate, so that the connection between clients, and port 60000 is encrypted.

Note: The upstream connection between caddy and 192.168.199.12:80 will remain HTTP (non-encrypted).

4. Error messages and/or full log output:

5. What I already tried:

I tried doing this:

{
    "apps": {
        "http": {
            "servers": {
                "my_server": {
                    "listen": [":60000"],
                    "routes": [
                        {
                            "handle": [{
                                "handler": "reverse_proxy",
                                "upstreams": [
                                    {
                                        "dial": "192.168.199.12:80"
                                    }
                                ]
                            }]
                        }
                    ]
                }
            }
        },
        "tls": {
            "certificates": {
                "load_files": [{
                    "certificate": "/path/to/cert/fullchain.pem",
                    "key": "/path/to/key/privkey.pem"
                }]  
            }   
        }
    }
}

But that had no effect. The page is still being served as HTTP on port 60000.
As far as I can tell, that loads the cert chain and key, but doesn’t use it.

I then tried adding a host matcher like so:

{
    "apps": {
        "http": {
            "servers": {
                "my_server": {
                    "listen": [":60000"],
                    "routes": [
                        {
                            "match": [{
                                "host": ["mydomain.com"]
                            }],
                            "handle": [{
                                "handler": "reverse_proxy",
                                "upstreams": [
                                    {
                                        "dial": "192.168.199.12:80"
                                    }
                                ]
                            }]
                        }
                    ]
                }
            }
        },
        "tls": {
            "certificates": {
                "load_files": [{
                    "certificate": "/path/to/cert/fullchain.pem",
                    "key": "/path/to/key/privkey.pem"
                }]  
            }   
        }
    }
}

where mydomain.com is my actual domain name.
The config validates with caddy validate --config /caddy.conf, but when I run it, I get this error:

run: loading initial config: loading new config: http app module: start: tcp: listening on :80: listen tcp :80: bind: address already in use

My port 80 is indeed in use (by nginx) for something unrelated. I’m not sure why caddy is trying to bind to port 80 though, since I’m still only telling it to listen on port 60000. My best guess is that it’s trying to acquire a cert from LetsEncrypt by using the HTTP challenge, and failing to bind to port 80.

What am I missing? Do I just need to somehow tell it to disable automatic HTTPS? Do I need to also tell it how to link my ssl cert/key with that particular handler? I figured it would match the “host” (mydomain.com) against the common name found inside the certificate, but maybe I’m wrong.

Update:

Adding

"automatic_https": {
    "disable": true
},

Has stopped giving me the error, allowing caddy to actually start, but when I hit the page on port 60000, I get absolutely nothing. I get a 200 OK with a blank body.

Oh I think I found out.

{
    "apps": {
        "http": {
            "servers": {
                "my_server": {
                    "listen": [":60000"],
                    "routes": [
                        {
                            "handle": [{
                                "handler": "reverse_proxy",
                                "upstreams": [
                                    {
                                        "dial": "192.168.199.12:80"
                                    }
                                ]
                            }]
                        }
                    ],
                    "tls_connection_policies": [{
                        "match": {
                            "sni": ["mydomain.com"]
                        }   
                    }]
                }
            }
        },
        "tls": {
            "certificates": {
                "load_files": [{
                    "certificate": "/path/to/cert/fullchain.pem",
                    "key": "/path/to/key/privkey.pem"
                }]  
            }   
        }
    }
}

Note the tls_connection_policies section.
It seems to be working now.

I now have a different error, but it appears to be unrelated. The page appears to be served as HTTPS now using my cert.

Just a tip, I’d recommend writing your config with Caddyfile, then using the caddy adapt command to get the JSON. It usually gives you a better starting point to get an idea of how your JSON config should look.

Glad you figured it out though!

What’s the error?

Thanks for the tip. I’ll keep that in mind.

My config file will be almost blank though, as everything will be configured dynamically through the API. It was just easier to explain the problem here without involving the API, as the problem was the config, not how to access the API.

I’m seeing 2 problems now:

  • While the majority of the page that I’m trying to load (html/javascript/css) loads fine, there are a few files that my browser appears to be trying to load as http (rather than https) and failing (because it’s considered mixed content). Either that, or they aren’t being looked for in the right place. Not sure yet
  • My websocket connection is being dropped for some reason

I haven’t had a chance to look into these problems yet, but seeing as how my setup involves the caddy reverse proxy, the which passes the connection off to an nginx proxy running on the upstream server (192.168.199.12 in my example), which uses web sockets to pass the connection off to a WSGI process, which passes it off to Flask, which uses a websockets extension, there are a lot of moving pieces, and at this point, the problem is unlikely to be Caddy. I’ll need to investigate further.

Thanks for the assistance.

That’s typically because your backend site specifies http:// on the assets in HTML. They should start with // (omitting the scheme) so that the browser requests the files using the same scheme as the originating page, or use absolute paths (skipping the domain), like /js/app.js instead of for example http://localhost/js/app.js.

Not really enough info to go on here, I can’t really suggest much.

Caddy v2 proxies websocket connections transparently, and this should work okay. Nginx might not be properly configured to make the additional hop for websockets, I don’t know.

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