1. The problem I’m having:
I have a homelab behind CGNAT, and up to this point I’ve gotten around this by using Rathole to tunnel to a VPS, which just forwards all its traffic to my homelab, which has a Caddy instance that routes the traffic to the correct application by subdomain.
The complication is that I’d like to also use the VPS to host some static content too, since it’s really easy to just tar -c | ssh tar -x arbitrary data into a folder for a second Caddy instance on the VPS to serve. The trouble arises from both Rathole and Caddy wanting to bind to ports 80 & 443 on the VPS. If I give the ports to Rathole, Caddy can’t serve the static content on the standard HTTP/S ports (obviously), and if I give the ports to Caddy, I can’t access anything served by my homelab on the standard HTTP/S ports.
Solution to this was to configure Caddy on the VPS to serve the static content first, and then reverse_proxy any other traffic to alternate ports (443 => 22443 and 80 => 22080), which Rathole can then forward to the homelab. This works, but it’s super evil. Here’s what I mean:
I want to do this…
rubiefawn.me {
root * /var/www/html/blog
file_server
}
# Didn't match the static site above, so forward this traffic
# to port 22443 so Rathole can send it to the homelab instead
:443 {
reverse_proxy :22443
}
…but I can’t get this to work for the life of me. I put the two main variations I tried under the ‘Error messages and/or full log output’ section, but there was a third I don’t remember that also gave me HTTP 308s. If I figure out how to replicate that one I’ll update this post.
However, specifying each individual subdomain in the reverse_proxy directive explicitly works fine for some reason:
rubiefawn.me {
root * /var/www/html/blog
file_server
}
jellyfin.frogify.net {
reverse_proxy https://jellyfin.frogify.net:22443
}
immich.frogify.net {
reverse_proxy https://immich.frogify.net:22443
}
notes.frogify.net {
reverse_proxy https://notes.frogify.net:22443
}
# ... so on and so forth for every single service
This is working, but it’s super evil and verbose and it feels like something I should be able to do with less. Is there a way to tell Caddy “please reverse-proxy this, but only change the port and preserve the domain, and also use HTTPS”? Is this actually all a horrible approach and there’s a fundamentally better way to solve this problem? What am I missing?
2. Error messages and/or full log output:
Results with :443 { reverse_proxy :22443 } and * { reverse_proxy https://:22443 }:
$ curl -vL https://jellyfin.frogify.net
* Host jellyfin.frogify.net:443 was resolved.
* IPv6: 2603:c020:b:63af:0:f6e1:164e:99b
* IPv4: 129.146.17.74
* Trying [2603:c020:b:63af:0:f6e1:164e:99b]:443...
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.
* closing connection #0
curl: (35) schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.
Results with jellyfin.frogify.net { reverse_proxy jellyfin.frogify.net:22443 }
$ curl -vL https://jellyfin.frogify.net
* Host jellyfin.frogify.net:443 was resolved.
* IPv6: 2603:c020:b:63af:0:f6e1:164e:99b
* IPv4: 129.146.17.74
* Trying [2603:c020:b:63af:0:f6e1:164e:99b]:443...
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* ALPN: server accepted http/1.1
* Connected to jellyfin.frogify.net (2603:c020:b:63af:0:f6e1:164e:99b) port 443
* using HTTP/1.x
> GET / HTTP/1.1
> Host: jellyfin.frogify.net
> User-Agent: curl/8.13.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 400 Bad Request
< Alt-Svc: h3=":443"; ma=2592000
< Via: 1.0 Caddy
< Date: Thu, 28 May 2026 05:54:04 GMT
< Content-Type: text/plain; charset=utf-8
< Transfer-Encoding: chunked
<
Client sent an HTTP request to an HTTPS server.
* Connection #0 to host jellyfin.frogify.net left intact
I remember seeing somewhere in the documentation something about Caddy assuming HTTP if not otherwise specified, and some directive to upgrade it, but I can’t remember what it’s called. Edit: Was thinking of http_redirect. That might help here, but it doesn’t solve the problem of having to explicitly provide the domain name to reverse_proxy.
3. Caddy version:
v2.11.3 h1:/vFbdjcs2DtzcWTIxHybf5R5TspYFFThlZffChyBFHg=
4. How I installed and ran Caddy:
sudo apt-get install caddy
a. System environment:
x86-64, Ubuntu 24.04
b. Command:
sudo systemctl start 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
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile --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:
rubiefawn.me {
root * /var/www/html/blog
file_server
}
remedy.rubiefawn.me {
root * /var/www/html/remedy
file_server
}
maps.rubiefawn.me {
root * /var/www/html/maps
file_server
}
remedy.neonfable.com {
redir https://remedy.rubiefawn.me permanent
}
map.neonfable.com {
redir https://maps.rubiefawn.me permanent
}
pepper.frogify.net {
redir https://pepper.rubiefawn.me permanent
}
# BEGIN EVIL HACK
jellyfin.frogify.net {
reverse_proxy https://jellyfin.frogify.net:22443
}
immich.frogify.net {
reverse_proxy https://immich.frogify.net:22443
}
notes.frogify.net {
reverse_proxy https://notes.frogify.net:22443
}
vaultwarden.frogify.net {
reverse_proxy https://vaultwarden.frogify.net:22443
}
pepper.rubiefawn.me {
reverse_proxy https://pepper.rubiefawn.me:22443
}
minecraft.frogify.net {
reverse_proxy https://minecraft.frogify.net:22443
}
fireball.frogify.net {
reverse_proxy https://fireball.frogify.net:22443
}