TL;DL
This Wiki explains how to enable https connections between hosts in a LAN with automatically renewed certificates. Just scroll down and go over the example Caddyfiles. They hopefully have enough comment to understand the idea
Introduction
If you have successfully followed the Wiki Using Caddy as a reverse proxy in a home network by @Matt, you have setup a reverse proxy that provides a TLS encrypted connection from the internet to that reverse proxy. This is mandatory and you shouldnât want it any less. However, beyond the reverse proxy your connections are non-TLS.
This Wiki is about -also- adding TLS between that reverse proxy (frontend) and the hosts behind it (backends) with automatically renewed certificates. Whether this is important, is up to you. Normally, you should be able to trust your own LAN. But there may be situations you donât want to take a chance. In my case I use the password manager Bitwarden and I didnât want to send my passwords unencrypted over the LAN.
This Wiki assumes you have managed to setup a reverse proxy as explained in the Wiki Using Caddy as a reverse proxy in a home network. From here I have added TLS encryption to the backend(s).
Definitions and Requirements
The definitions and requirements from the Wiki mentioned above apply for this Wiki too. This Wiki has one extra requirement. You need to setup either a local DNS or split DNS.
With your own (split) DNS you can resolve Fully Qualified Domain Names (FQDN) in your LAN rather than using IP addresses. For example:
nextcloud.my.example.com
â 192.168.0.4
or
office.roadrunner
â 192.168.0.4
Going into details about DNS or split DNS is out of the scope of this Wiki. Cloudflare has a pretty good explanation what is DNS and more about split DNS can be found here.
Two well-known DNS systems are:
I use unbound because it is by default available in OPNsense and comes with a handy GUI.
Alternatively, you could also fill up your hosts files but I have not tested this myself. It will be much more work to maintain though.
Topology
The below figure is somewhat similar to the version without TLS in the LAN. There is a frontend host and two backend hosts. Each host can be virtual or physical. The frontend hosts acts as reverse proxy to the outside WWWorld and provides trusted certificates from Letâs Encrypt. The backend hosts are hosting several services.
The difference is that the same frontend Caddy service is also setup as a certificate authority using the ACME protocol (powered by smallstep) to provide certificates for the backends.
Each backend also has a Caddy service running that requests the certificate and to either act as a web server or reverse proxy (or both) to the desired services on that backend.
Note that the hosts with docker containers is just presented as a possibility. This Wiki will focus on the 2nd host with Nextcloud on it.
DNS
To set up the DNS you can follow the same guidance as in Using Caddy as a reverse proxy in a home network.
Local (Split) DNS
Each host should be given a FQDN to resolve the IP of the corresponding host in the local network. In this example I created:
caddy.roadrunner
â 192.168.0.2
to reach the frontend (downstream) Caddy service
services.roadrunner
â 192.168.0.3
to reach the 1st backend (upstream) host with services
office.roadrunner
â 192.168.2.4
to reach the 2nd backend (upstream) host with services
I created domain names for the hosts because pointing directly to the IP addresses didnât work with the ACME server. I was not able to get certificates for the backends.
Services
Services can either be installed natively or you can use docker containers. The Caddy services should not be in a docker container because this will complicate the automated certificate renewal.
Port forwarding
To set up the port forwarding you can follow the same guidance as in Using Caddy as a reverse proxy in a home network.
Local HTTPS
To enable local HTTPS you need to enable the ACME server in the Frontend Caddy service in the Caddyfile. In the backend you have to point to this server in the Caddyfile to request a certificate.
Frontend
To enable the ACME server in the frontend, include the acme_server directive in the Caddyfile.
# ACME Server
caddy.roadrunner {
acme_server
tls internal
}
Note that the FQDN caddy.roadrunner
is used here.
Secondly, you need define or update the FQDN where Caddy listens to and reverse proxies accordingly with TLS.
nextcloud.my.example.com {
reverse_proxy https://office.roadrunner {
header_up Host {upstream_hostport}
}
}
In the above example, connections from nextcloud.my.example.com
coming from the WAN are being forwarded to office.roadrunner
with TLS enabled.
There is one extra line header_up Host {upstream_hostport}
which is a host header with placeholder that will override the host header with the host name in the proxy upstream.
Read my question topic for more info.
Backends
There are two ways to configure the backends to request a certificate from the frontend.
- By setting the TLS directive for each domain that is configured in the Caddyfile, for example;
office.roadrunner {
tls {
ca https://caddy.roadrunner/acme/local/directory
ca_root /etc/ssl/certs/root.crt
}
Here the FQDN office.roadrunner
is told to request a certificate from caddy.roadrunner
.
- By setting one directive for the whole Caddyfile in the global section.
# Global Option Block
# TLS Options
acme_ca https://caddy.roadrunner/acme/local/directory
acme_ca_root /etc/ssl/certs/root.crt
Here a certificate is requested from caddy.roadrunner
for each defined domain (sites) in the Caddyfile.
To request a certificate, a root certificate from the server is required. (Read this blog from Mike Malone to understand why). Once you have started the Caddy service on the frontend for the first time and there are no errors, a file named root.crt
is generated in .local/share/caddy/pki/authorities/local
NOTE: The absolute path for the certificate depends on how you run Caddy. Typically when you use systemd the absolute path will be /var/lib/caddy/.local/share/caddy/pki/authorities/local
but if you run caddy with caddy start
the path .local/share/caddy/pki/authorities/local
You will have to copy this file to the backend and point to it in the Caddyfile. In the above example the file root.crt
has been copied to /etc/ssl/certs/root.crt
will be created in the in the directory you initiated the command (and where the Caddyfile
is located).
If you are working with SSH terminals, you can just copy / paste the content of root.crt
to the other terminal with your favourite txt editor, ie vim. The content will look something like this:
-----BEGIN CERTIFICATE-----
Mkd2PrifjSodwIBAgIQICdiSD4Yf9/2x0JL2MKkqzAKBggqhkjOPQQDAjAwMS4w
LAYDVQQDEyVDYWRkeSBMb2NhbCBBdXRob3JpdHkgLSAyMDIxIEVDQyBSb290MB4X
DTIxMDIwMzE3NDKEWRJ#fFJEklejejelWo3oFj3NTYyMlowMDEuMCwGA1UEAxMlQ2FkZHkg
TG9jYWwgQXV0aG9yaXR5IC0gMjAyMSBFQ0MgUm9vdDBZMBMGByqGSM49AgEGCCqG
SM49AwEHA0IABF6n6K32IongBmQmrFuTKCL0LlNWGY/aLUsWQtb6WazWMtEJxoH/
9FTO9zoUl5tPGyKU7yty6xFkejelfFDpoa[fpfWPOfkjewBDMA4GA1UdDwEB/wQEAwIBBjAS
BgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBShK2n24nbgtXrJTNjmfHIPfUTh
4jAKBggqhkjOPQQDAgNIADBFAiEAvAZzPc05PTyL2ilxcwIB5LEq9HDgl/eEtQok
Fu1VERUCIC4JxfRKk4PfhRcjBJ8Rz0cjYFJR9fOV90wFo9FEKw[pwq
-----END CERTIFICATE-----
Now the only thing left to do is to add a service to the defined domain office.roadrunner
. You could directly point to a fileserver (Caddy is also a fileserver) or reverse proxy to another service on this backend. In this example I will use Nextcloud. The config is heavily inspired by this great Wiki by @basil
office.roadrunner {
root * /var/www/html
php_fastcgi 127.0.0.1:9000 {
env front_controller_active true # Remove index.php form url
}
file_server
header Strict-Transport-Security max-age=31536000; # enable HSTS
# .htaccess / data / config / ... shouldn't be accessible from outside
@forbidden {
path /.htaccess
path /data/*
path /config/*
path /db_structure
path /.xml
path /README
path /3rdparty/*
path /lib/*
path /templates/*
path /occ
path /console.php
}
respond @forbidden 404
}
Test
After this you should be ready to caddy run
or caddy reload
if Caddy is already running.
When someone enters https://nextcloud.my.example.com in his browser a TLS encrypted connection is made to the frontend host with a certificate provided by Letâs Encrypt. The connection is then forwarded with TLS from the local ACME server to https://office.roadrunner on the backend host.
For debugging you can enable the debug
option in the global section of each Caddy service.
# Global Option Block
{
debug
}
Caddyfiles + comments
For completeness I have copied two full Caddyfiles for reference.
Frontend
# Global Option Block
{
debug # enable debugging. Comment out when everything is working
}
# ACME Server
acme.roadrunner { # defining FQDN for ACME server
acme_server # defining the ACME server
tls internal
}
# Nextcloud
nextcloud.my.example.com { # defining incoming FQDN
reverse_proxy https://office.roadrunner {
header_up Host {upstream_hostport}
}
}
Backend
# Global Option Block
{
# General Option
debug
# TLS Options
acme_ca https://acme.roadrunner/acme/local/directory # point to ACME server
acme_ca_root /etc/ssl/certs/root.crt # define root certificate
}
office.roadrunner { # FQDN for the backend
root * /var/www/html
file_server
php_fastcgi 127.0.0.1:9000 {
env front_controller_active true # Remove index.php form url
}
header {
Strict-Transport-Security max-age=31536000; # enable HSTS
}
# .htaccess / data / config / ... shouldn't be accessible from outside
@forbidden {
path /.htaccess
path /data/*
path /config/*
path /db_structure
path /.xml
path /README
path /3rdparty/*
path /lib/*
path /templates/*
path /occ
path /console.php
}
respond @forbidden 404
}
Disclaimer
Most of the information in this Wiki can be found in either the official documentation or in the many posts and Wikiâs on this forum. I tried to link as much as possible to the original source. Together with a lot of help from @francislavoie and @matt I was able to get the local https to work.
To come
In seperate Wikiâs I will post more Caddyfile examples how to host Nextcloud with Collabora, Bitwarden and Papermerge.
Caddy is really great in doing this because itâs got everything. Most of the times you can strip down the docker containers and remove Apache, Nginx Trafic and Cerbot and just let (one) Caddy service do the job.