1. The problem I’m having:
First off, I’m new to proxies in general.
I’m trying to put insecure services behind a reverse proxy (or proxies?) in my homelab, using my internal Active Directory Domain Services PKI, not external CAs.
Everything is internal, and there’s no Docker. Meaning no popular public CAs, Cloudflare, etc.
Right now, I’m trying to get my Joplin server (Proxmox LXC, installed using community scripts. There’s no Apache or Nginx.) behind a reverse proxy just to accomplish that, but I’ve been unsuccessful.
My first question would be: is this even something Caddy can do?
According to a short instruction linked below, I should be able to proxy my service running on localhost HTTP 22300 to an FQDN HTTPS 443, using my internal certificates. I have installed my root CA in the OS certificate store, and the server certificates are placed in /etc/caddy.
I converted the four-line configuration to JSON just because I prefer it that way.
The server certificate is in the PEM format and has the root CA and server certificate stored in a single file to form a chain. I’m not sure of the order. The root CA is on top, and the server certificate below it. There are no intermediate CAs in my homelab.
I have restarted Joplin and Caddy after each change.
Off-topic: Ideally, Caddy would request certificates from the ADCS. I saw a plugin for it on the Caddy download page, but it was for an older version and didn’t work.
2. Error messages and/or full log output:
# Journal output
Oct 19 19:50:26 aquarius systemd[1]: Starting caddy.service - Caddy...
Oct 19 19:50:27 aquarius caddy[859672]: caddy.HomeDir=/var/lib/caddy
Oct 19 19:50:27 aquarius caddy[859672]: caddy.AppDataDir=/var/lib/caddy/.local/share/caddy
Oct 19 19:50:27 aquarius caddy[859672]: caddy.AppConfigDir=/var/lib/caddy/.config/caddy
Oct 19 19:50:27 aquarius caddy[859672]: caddy.ConfigAutosavePath=/var/lib/caddy/.config/caddy/autosave.json
Oct 19 19:50:27 aquarius caddy[859672]: caddy.Version=v2.11.7 h1:yj0Y4fYZGPkSvibBJ1sTWE33xC0fxztVyXEW5iIdUT4=
Oct 19 19:50:27 aquarius caddy[859672]: runtime.GOOS=linux
Oct 19 19:50:27 aquarius caddy[859672]: runtime.GOARCH=amd64
Oct 19 19:50:27 aquarius caddy[859672]: runtime.Compiler=gc
Oct 19 19:50:27 aquarius caddy[859672]: runtime.NumCPU=2
Oct 19 19:50:27 aquarius caddy[859672]: runtime.GOMAXPROCS=2
Oct 19 19:50:27 aquarius caddy[859672]: runtime.Version=go1.26.8
Oct 19 19:50:27 aquarius caddy[859672]: os.Getwd=/
Oct 19 19:50:27 aquarius caddy[859672]: LANG=en_US.UTF-8
Oct 19 19:50:27 aquarius caddy[859672]: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
Oct 19 19:50:27 aquarius caddy[859672]: NOTIFY_SOCKET=/run/systemd/notify
Oct 19 19:50:27 aquarius caddy[859672]: USER=caddy
Oct 19 19:50:27 aquarius caddy[859672]: LOGNAME=caddy
Oct 19 19:50:27 aquarius caddy[859672]: HOME=/var/lib/caddy
Oct 19 19:50:27 aquarius caddy[859672]: INVOCATION_ID=8d0ee4f8379d490eac4d5558e9a6de0f
Oct 19 19:50:27 aquarius caddy[859672]: JOURNAL_STREAM=8:645183394
Oct 19 19:50:27 aquarius caddy[859672]: SYSTEMD_EXEC_PID=859672
Oct 19 19:50:27 aquarius caddy[859672]: MEMORY_PRESSURE_WATCH=/sys/fs/cgroup/system.slice/caddy.service/memory.pressure
Oct 19 19:50:27 aquarius caddy[859672]: MEMORY_PRESSURE_WRITE=c29tZSAyMDAwMDAgMjAwMDAwMAA=
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.022144,"msg":"maxprocs: Leaving GOMAXPROCS=2: CPU quota undefined"}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.0221617,"msg":"GOMEMLIMIT is updated","GOMEMLIMIT":30215857766,"previous":9223372036854775807}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.0221665,"msg":"using config from file","file":"/etc/caddy/Caddyfile.json"}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.0227416,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//[::1]:2019","//127.0.0.1:2019","//localhost:2019"]}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"debug","ts":1792428627.0245883,"logger":"tls","msg":"stapling OCSP","error":"no OCSP stapling for [aquarius.homelab.local notes.homelab.local 10.0.2.7]: no OCSP server specified in certificate"}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"debug","ts":1792428627.0246146,"logger":"events","msg":"event","name":"cached_unmanaged_cert","id":"3231c958-2e35-46fa-b05b-c7d79beb6915","origin":"tls","data":{"sans":["aquarius.homelab.local","notes.homelab.local","10.0.2.7"]}}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"debug","ts":1792428627.0246432,"logger":"tls.cache","msg":"added certificate to cache","subjects":["aquarius.homelab.local","notes.homelab.local","10.0.2.7"],"expiration":1855529172,"managed":false,"issuer_key":"","hash":"0dd08ab30e2b6b033deea85dd99f774b8e0b6647fb828800543d06eeea762a79","cache_size":1,"cache_capacity":10000}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.0248914,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0x3f5b76036080"}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.0251524,"logger":"http.auto_https","msg":"skipping automatic certificate management because one or more matching certificates are already loaded","domain":"aquarius.homelab.local","server_name":"srv0"}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.0252247,"logger":"http.auto_https","msg":"skipping automatic certificate management because one or more matching certificates are already loaded","domain":"notes.homelab.local","server_name":"srv0"}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.0252345,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"debug","ts":1792428627.0252638,"logger":"http.auto_https","msg":"adjusted config","tls":{"automation":{"policies":[{}]}},"http":{"servers":{"remaining_auto_https_redirects":{"listen":[":80"],"routes":[{},{}]},"srv0":{"listen":[":443"],"routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"127.0.0.1:22300"}]}]}]}],"terminal":true}],"tls_connection_policies":[{"match":{"sni":["aquarius.homelab.local","notes.homelab.local"]},"certificate_selection":{"any_tag":["cert0"]}},{}],"automatic_https":{}}}}}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"debug","ts":1792428627.025798,"logger":"http","msg":"starting server loop","address":"[::]:443","tls":true,"http3":false}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.025835,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.025915,"msg":"failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 7168 kiB, got: 416 kiB). See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details."}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.026003,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"debug","ts":1792428627.0260506,"logger":"http","msg":"starting server loop","address":"[::]:80","tls":false,"http3":false}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"warn","ts":1792428627.0260592,"logger":"http","msg":"HTTP/2 skipped because it requires TLS","network":"tcp","addr":":80"}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"warn","ts":1792428627.0260623,"logger":"http","msg":"HTTP/3 skipped because it requires TLS","network":"tcp","addr":":80"}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.026065,"logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"debug","ts":1792428627.0260944,"logger":"events","msg":"event","name":"started","id":"ab0361bf-cd97-4387-8c1c-4bdd5a01b475","origin":"","data":null}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.026252,"msg":"autosaved config (load with --resume flag)","file":"/var/lib/caddy/.config/caddy/autosave.json"}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.026863,"msg":"serving initial configuration"}
Oct 19 19:50:27 aquarius systemd[1]: Started caddy.service - Caddy.
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.0442584,"logger":"tls","msg":"storage cleaning happened too recently; skipping for now","storage":"FileStorage:/var/lib/caddy/.local/share/caddy","instance":"0e2ac37b-cd88-40c3-ac19-d0dbd44f54b4","try_again":1792515027.0442412,"try_again_in":86399.999999524}
Oct 19 19:50:27 aquarius caddy[859672]: {"level":"info","ts":1792428627.0443585,"logger":"tls","msg":"finished cleaning storage units"}
# curl attempts
curl -vL aquarius.homelab.local:443
* Host aquarius.homelab.local:443 was resolved.
* IPv6: (none)
* IPv4: 10.0.2.7
* Trying 10.0.2.7:443...
* Connected to aquarius.homelab.local (10.0.2.7) port 443
* using HTTP/1.x
> GET / HTTP/1.1
> Host: aquarius.homelab.local:443
> User-Agent: curl/8.14.1
> Accept: */*
>
* Request completely sent off
* HTTP 1.0, assume close after body
< HTTP/1.0 400 Bad Request
<
Client sent an HTTP request to an HTTPS server.
* shutting down connection #0
curl -vL notes.homelab.local:443
* Host notes.homelab.local:443 was resolved.
* IPv6: (none)
* IPv4: 10.0.2.7
* Trying 10.0.2.7:443...
* Connected to notes.homelab.local (10.0.2.7) port 443
* using HTTP/1.x
> GET / HTTP/1.1
> Host: notes.homelab.local:443
> User-Agent: curl/8.14.1
> Accept: */*
>
* Request completely sent off
* HTTP 1.0, assume close after body
< HTTP/1.0 400 Bad Request
<
Client sent an HTTP request to an HTTPS server.
* shutting down connection #0
curl -vL 127.0.0.1:22300
* Trying 127.0.0.1:22300...
* Connected to 127.0.0.1 (127.0.0.1) port 22300
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 127.0.0.1:22300
> User-Agent: curl/8.14.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 404 Not Found
< Vary: Origin
< Content-Security-Policy: frame-ancestors 'none'
< X-Frame-Options: DENY
< Content-Type: text/plain; charset=utf-8
< Content-Length: 38
< Date: Mon, 19 Oct 2026 15:28:39 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
* Connection #0 to host 127.0.0.1 left intact
Invalid origin: http://127.0.0.1:22300
3. Caddy version:
v2.11.7 h1:yj0Y4fYZGPkSvibBJ1sTWE33xC0fxztVyXEW5iIdUT4=
4. How I installed and ran Caddy:
apt install gnupg curl apt-transport-https debian-keyring debian-archive-keyring -y
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
wget -qO - https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt | tee /etc/apt/sources.list.d/caddy.list
apt update
apt install caddy -y
a. System environment:
DEBIAN_VERSION_FULL=13.7
> ps -p 1 -o comm=
systemd
systemd seems to be present and active.
No Docker.
aquarius.homelab.local is an A record in the AD DNS.
notes.homelab.local is a CNAME record pointing to the A record in the AD DNS.
b. Command:
# Listening ports
ss -tlpn | grep -P '80|443|22300'
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=806217,fd=6))
LISTEN 0 511 *:22300 *:* users:(("node /opt/jopli",pid=858469,fd=25))
LISTEN 0 4096 *:443 *:* users:(("caddy",pid=859164,fd=6))
LISTEN 0 4096 *:80 *:* users:(("caddy",pid=859164,fd=9))
LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=806217,fd=7))
# Cert key ACE modification
/etc/caddy> chown root:caddy notes.key
/etc/caddy> chmod 640 notes.key
# Journal showed that Caddy didn't have permissions to create files in /var/lib/caddy
/var/lib> mkdir caddy
/var/lib> chown root:caddy caddy
/var/lib> chmod 775 caddy
c. Service/unit/compose file:
# caddy.service
#
# For using Caddy with a config file.
#
# Make sure the ExecStart and ExecReload commands are correct
# for your installation.
#
# See https://caddyserver.com/docs/install for instructions.
#
# WARNING: This service does not use the --resume flag, so if you
# use the API to make changes, they will be overwritten by the
# Caddyfile next time the service is restarted. If you intend to
# use Caddy's API to configure it, add the --resume flag to the
# `caddy run` command or use the caddy-api.service file instead.
[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target
[Service]
Type=notify
User=caddy
Group=caddy
ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile.json
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile.json --force
TimeoutStopSec=5s
LimitNOFILE=1048576
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
d. My complete Caddy config:
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"aquarius.homelab.local",
"notes.homelab.local"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"upstreams": [
{
"dial": "127.0.0.1:22300"
}
]
}
]
}
]
}
],
"terminal": true
}
],
"tls_connection_policies": [
{
"match": {
"sni": [
"aquarius.homelab.local",
"notes.homelab.local"
]
},
"certificate_selection": {
"any_tag": [
"cert0"
]
}
},
{}
]
}
}
},
"tls": {
"certificates": {
"load_files": [
{
"certificate": "/etc/caddy/notes.pem",
"key": "/etc/caddy/notes.key",
"tags": [
"cert0"
]
}
]
}
}
}
}
5. Links to relevant resources:
I was given instructions on how to use Caddy in its basic form here: