`storage file_system` doesn't actually change the storage location?

1. Output of caddy version:

root@FriendlyWrt:~# caddy version
v2.6.2 h1:wKoFIxpmOJLGl3QXoo6PNbYvGW4xLEgo32GPBEjWL8o=

2. How I run Caddy:

Via /etc/rc.local: /usr/bin/caddy start --resume --config /srv/Caddyfile

a. System environment:

root@FriendlyWrt:~# cat /etc/rc.local | grep caddy
/usr/bin/caddy start --resume --config /srv/Caddyfile
root@FriendlyWrt:~# cat /etc/openwrt_release
DISTRIB_ID='OpenWrt'
DISTRIB_RELEASE='22.03.2'
DISTRIB_REVISION='r19803-9a599fee93'
DISTRIB_TARGET='rockchip/armv8'
DISTRIB_ARCH='aarch64_generic'
DISTRIB_DESCRIPTION='OpenWrt 22.03.2 r19803-9a599fee93'
DISTRIB_TAINTS='busybox'

b. Command:

Just the rc.local one with the occasional validate/reload/...

c. Service/unit/compose file:

rc.local

d. My complete Caddy config:

# Globals
{
        log {
                level info
                output file /var/log/caddy.log {
                        roll_size 10mb
                        roll_keep 10
                        roll_keep_for 720h
                }
        }
        #trusted_proxies 192.168.222.0/24
        storage file_system /var/lib/caddy/.local/share/caddy
        auto_https disable_certs
}
# Rest is mostly unrelated, except:
# Basic ping-pong
echo.birb.it {
        respond "hello"
}

video.birb.it:80 {
        reverse_proxy * localhost:8096
}

grocy.birb.it:80 {
        root * /sdcard/srv/grocy/public
        import php
        file_server
        trusted_proxies 192.168.222.0/24
}

3. The problem I’m having:

I used storage file_system to link my local caddy to the reosurces of my remote one via SSHFS.

First, for comparison:

root@FriendlyWrt:/mnt/vps/var/lib/caddy/.local/share/caddy# ls -l
drwx------    1 www      992           4096 Nov 20 13:25 acme
drwx------    1 www      992           4096 Nov 20 13:26 certificates
drwx------    1 www      992           4096 Dec 13 17:29 locks
drwx------    1 www      992           4096 Dec 13 17:11 ocsp

/mnt/vps is my mountpoint. Now, the “real” version as my main server sees it:

root@birb:/var/lib/caddy/.local/share/caddy# ls -l
total 24
drwx------ 6 caddy caddy 4096 Dec 13 13:29 .
drwx------ 3 caddy caddy 4096 Nov 20 09:24 ..
drwx------ 5 caddy caddy 4096 Nov 20 09:25 acme
drwx------ 3 caddy caddy 4096 Nov 20 09:26 certificates
drwx------ 2 caddy caddy 4096 Dec 13 13:29 locks
drwx------ 2 caddy caddy 4096 Dec 13 13:11 ocsp

On my VPS, caddy can read/write the directories, which is to be expected. However, on my local server, the UID is interpreted as the user www - which seems to be more of a funny coincidence than anything, but this also means that technically my home router should not be able to read the files. However - it doesn’t read them at all, because it never changed!

4. Error messages and/or full log output:

root@FriendlyWrt:/var/log# tail -f caddy.log

2022/12/13 21:05:45.573 info    admin   admin endpoint started  {"address": "localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2022/12/13 21:05:45.574 info    http    server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS {"server_name": "srv0", "https_port": 443}
2022/12/13 21:05:45.574 warn    http    skipping automated certificate management for server because it is disabled     {"server_name": "srv0"}
2022/12/13 21:05:45.574 info    http    enabling automatic HTTP->HTTPS redirects        {"server_name": "srv0"}
2022/12/13 21:05:45.574 warn    http    server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server {"server_name": "srv1", "http_port": 80}
2022/12/13 21:05:45.574 info    tls.cache.maintenance   started background certificate maintenance      {"cache": "0x4000736230"}
2022/12/13 21:05:45.576 info    http    enabling HTTP/3 listener        {"addr": ":443"}
2022/12/13 21:05:45.576 info    tls     cleaning storage unit   {"description": "FileStorage:/var/lib/caddy/.local/share/caddy"}
2022/12/13 21:05:45.576 info    tls     finished cleaning storage units
2022/12/13 21:05:45.576 info    http.log        server running  {"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2022/12/13 21:05:45.576 info    http.log        server running  {"name": "srv1", "protocols": ["h1", "h2", "h3"]}
2022/12/13 21:05:45.577 info    autosaved config (load with --resume flag)      {"file": "/root/.config/caddy/autosave.json"}
2022/12/13 21:05:45.577 info    serving initial configuration

The paths are off and not what I configured. So, I did this:

root@FriendlyWrt:/var/log# caddy environ
caddy.HomeDir=/root
caddy.AppDataDir=/root/.local/share/caddy
caddy.AppConfigDir=/root/.config/caddy
caddy.ConfigAutosavePath=/root/.config/caddy/autosave.json
caddy.Version=v2.6.2 h1:wKoFIxpmOJLGl3QXoo6PNbYvGW4xLEgo32GPBEjWL8o=
runtime.GOOS=linux
runtime.GOARCH=arm64
runtime.Compiler=gc
runtime.NumCPU=8
runtime.GOMAXPROCS=8
runtime.Version=go1.19.4
os.Getwd=/var/log

SHELL=/bin/bash
ENV=/etc/shinit
PWD=/var/log
LOGNAME=root
HOME=/root
SSH_PUBKEYINFO=ingwie
SSH_CONNECTION=192.168.2.212 1629 192.168.2.1 22
TERM=xterm-256color
USER=root
SHLVL=1
COMPOSER_ALLOW_SUPERUSER=1
PS1=\[\e]0;\u@\h: \w\a\]\u@\h:\w\$
SSH_CLIENT=192.168.2.212 1629 22
PATH=/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/bin
SSH_TTY=/dev/pts/3
_=/usr/bin/caddy
OLDPWD=/root

Note: caddy.AppDataDir=/root/.local/share/caddy
This should’ve changed. I also verified with caddy run --environ ... - and the value still was the same!

5. What I already tried:

I looked at the docs and simply used what was written there - and Caddy did not reject it during start.

Did I overlook something?

6. Links to relevant resources:

Edit 1

I noticed I had not prefixed the storage with /mnt/vps. Derp… That said, after changing it, not a whole lot changed; in fact, nothing of note did.

Logs:

2022/12/13 21:12:34.271 info    admin   admin endpoint started  {"address": "localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2022/12/13 21:12:34.271 warn    http    server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server {"server_name": "srv1", "http_port": 80}
2022/12/13 21:12:34.271 info    http    server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS {"server_name": "srv0", "https_port": 443}
2022/12/13 21:12:34.271 warn    http    skipping automated certificate management for server because it is disabled     {"server_name": "srv0"}
2022/12/13 21:12:34.271 info    http    enabling automatic HTTP->HTTPS redirects        {"server_name": "srv0"}
2022/12/13 21:12:34.272 info    tls.cache.maintenance   started background certificate maintenance      {"cache": "0x4000287030"}
2022/12/13 21:12:34.274 info    http    enabling HTTP/3 listener        {"addr": ":443"}
2022/12/13 21:12:34.274 info    tls     cleaning storage unit   {"description": "FileStorage:/mnt/vps/var/lib/caddy/.local/share/caddy"}
2022/12/13 21:12:34.274 info    http.log        server running  {"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2022/12/13 21:12:34.274 info    http.log        server running  {"name": "srv1", "protocols": ["h1", "h2", "h3"]}
2022/12/13 21:12:34.275 info    autosaved config (load with --resume flag)      {"file": "/root/.config/caddy/autosave.json"}
2022/12/13 21:12:34.275 info    serving initial configuration
2022/12/13 21:12:35.269 info    tls     finished cleaning storage units

curl from the router itself:

root@FriendlyWrt:~# curl -v https://echo.birb.it
* ssl_handshake returned - mbedTLS: (-0x7780) SSL - A fatal alert message was received from our peer
curl: (35) ssl_handshake returned - mbedTLS: (-0x7780) SSL - A fatal alert message was received from our peer
root@FriendlyWrt:~# curl -v https://grocy.birb.it
* ssl_handshake returned - mbedTLS: (-0x7780) SSL - A fatal alert message was received from our peer
curl: (35) ssl_handshake returned - mbedTLS: (-0x7780) SSL - A fatal alert message was received from our peer

And, the environment flags:

root@FriendlyWrt:~# caddy run --environ --config /srv/Caddyfile
caddy.HomeDir=/root
caddy.AppDataDir=/root/.local/share/caddy
caddy.AppConfigDir=/root/.config/caddy
caddy.ConfigAutosavePath=/root/.config/caddy/autosave.json
caddy.Version=v2.6.2 h1:wKoFIxpmOJLGl3QXoo6PNbYvGW4xLEgo32GPBEjWL8o=
runtime.GOOS=linux
runtime.GOARCH=arm64
runtime.Compiler=gc
runtime.NumCPU=8
runtime.GOMAXPROCS=8
runtime.Version=go1.19.4
os.Getwd=/root

So much for fixing that “typo”…

Edit 2

I attempted to just copy over the entire directory and chown it to my root user. Then commenting out the storage option and using a full stop/start instead of reload - without --resume - to try and see if that did anything. And… it did not. The same errors still occur.

I am quite lost at this point… All I want to do is have the same certificates for my local and remote access. I will be spending a long time away from home, and accessing those services is going to be important. So I really want to just get this working…

That shows you the environment if you were to run Caddy with the current user. That’s not the same thing as when you’re running it as a service.

Looks at your service logs right when it’s starting up, you’ll see the environment dumped out because of the --environ flag.

But either way, I think caddy.AppDataDir will always show the default storage location based on the current user, and never the one from the config, because the config is loaded after the environment is printed out.

Edit: Sorry I misread, you’re not using systemd. The explanation still applies though.

I don’t think there’s a problem here, you’re just being misled by the environment showing the default location, not the configured location.

Actually, you do see the change here:

True - that did happen! I noticed that a while later but I still get errornous responses when trying to make HTTPS requests to the server. I set up echo.birb.it to reply with the default HTTPS config. You can try yourself to see what happens.

# Router:
# curl https://echo.birb.it
curl: (35) ssl_handshake returned - mbedTLS: (-0x7780) SSL - A fatal alert message was received from our peer

# Server:
root@birb:~# curl https://echo.birb.it
root@birb:~# curl -v https://echo.birb.it
*   Trying 185.185.127.216:443...
* Connected to echo.birb.it (185.185.127.216) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=echo.birb.it
*  start date: Dec 13 18:11:16 2022 GMT
*  expire date: Mar 13 18:11:15 2023 GMT
*  subjectAltName: host "echo.birb.it" matched cert's "echo.birb.it"
*  issuer: C=US; O=Let's Encrypt; CN=R3
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x558ebbe782c0)
> GET / HTTP/2
> Host: echo.birb.it
> user-agent: curl/7.74.0
> accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 502
< alt-svc: h3=":443"; ma=2592000
< server: Caddy
< content-length: 0
< date: Wed, 14 Dec 2022 00:37:38 GMT
<
* Connection #0 to host echo.birb.it left intact

# Other server:
root@drachennetz:~# curl https://echo.birb.it
root@drachennetz:~# curl -v https://echo.birb.it
*   Trying 185.185.127.216:443...
* TCP_NODELAY set
* Connected to echo.birb.it (185.185.127.216) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=echo.birb.it
*  start date: Dec 13 18:11:16 2022 GMT
*  expire date: Mar 13 18:11:15 2023 GMT
*  subjectAltName: host "echo.birb.it" matched cert's "echo.birb.it"
*  issuer: C=US; O=Let's Encrypt; CN=R3
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x55daf659b210)
> GET / HTTP/2
> Host: echo.birb.it
> user-agent: curl/7.68.0
> accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 502
< alt-svc: h3=":443"; ma=2592000
< server: Caddy
< content-length: 0
< date: Wed, 14 Dec 2022 00:38:15 GMT
<
* Connection #0 to host echo.birb.it left intact

The configuration:

# Router:
echo.birb.it {
  respond "router here!"
}

# Server:
echo.birb.it {
  reverse_proxy * https://192.168.222.11 # this is the IP the router has in the VPN
}

By the way, requesting through the VPN from the server to the router:

root@birb:~# curl -v -H "Host: echo.birb.it" https://192.168.222.11
*   Trying 192.168.222.11:443...
* Connected to 192.168.222.11 (192.168.222.11) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS alert, internal error (592):
* error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error
* Closing connection 0
curl: (35) error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error

That means the TLS connection was fine, but the proxy failed to connect to your upstream. That’s not a problem with the storage.

That’s not the right way to do it, because then curl will send 192.168.222.11 in TLS SNI, which Caddy will not be able to do anything with. It won’t be able to find a certificate that has 192.168.222.11 in its SAN.

You need to use the --resolve option instead to force it to use a different IP address than what DNS would resolve to:

curl --resolve echo.birb.it:443:192.168.222.11 https://echo.birb.it
root@birb:~# curl --resolve echo.birb.it:443:192.168.222.11 https://echo.birb.it
curl: (35) error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error

Thank you for the hint though! That doesn’t seem to fix the error though.

My guess right now is that Caddy on the router is having troubble reading the certificates. But, there is no error in the log at all, so I wouldn’t know what the problem is (although I have debug in my global options). Is there a way I can make Caddy even more verbose?

If you have debug on, you did see logs on every TLS connection. If you’re not seeing any logs, then you’re not hitting the server you think you are.

Turns out I had to change level info to level debug in my log options too.

And, this revealed this:

{"level":"debug","ts":1671036533.006517,"logger":"events","msg":"event","name":"tls_get_certificate","id":"8d8365eb-1286-4ca7-b7e1-5951cc353091","origin":"tls","data":{"client_hello":{"CipherSuites":[49196,49195,49200,49199,159,158,49188,49187,49192,49191,49162,49161,49172,49171,157,156,61,60,53,47,10],"ServerName":"echo.birb.it","SupportedCurves":[29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[2052,2053,2054,1025,1281,513,1027,1283,515,514,1537,1539],"SupportedProtos":["http/1.1"],"SupportedVersions":[771,770,769],"Conn":{}}}}
{"level":"debug","ts":1671036533.0068395,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"echo.birb.it"}
{"level":"debug","ts":1671036533.0068905,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.birb.it"}
{"level":"debug","ts":1671036533.0069237,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.it"}
{"level":"debug","ts":1671036533.0069556,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.*"}
{"level":"debug","ts":1671036533.0069897,"logger":"tls.handshake","msg":"all external certificate managers yielded no certificates and no errors","remote_ip":"192.168.2.212","remote_port":"3066","sni":"echo.birb.it"}
{"level":"debug","ts":1671036533.0070362,"logger":"tls.handshake","msg":"no certificate matching TLS ClientHello","remote_ip":"192.168.2.212","remote_port":"3066","server_name":"echo.birb.it","remote":"192.168.2.212:3066","identifier":"echo.birb.it","cipher_suites":[49196,49195,49200,49199,159,158,49188,49187,49192,49191,49162,49161,49172,49171,157,156,61,60,53,47,10],"cert_cache_fill":0,"load_if_necessary":true,"obtain_if_necessary":true,"on_demand":false}
{"level":"debug","ts":1671036533.0073328,"logger":"http.stdlib","msg":"http: TLS handshake error from 192.168.2.212:3066: no certificate available for 'echo.birb.it'"}

So, it couldn’t find a certificate - but it should in fact be there.

root@FriendlyWrt ~# cat /srv/Caddyfile | grep file_system
  storage file_system /mnt/vps/var/lib/caddy/.local/share/caddy
root@FriendlyWrt ~# tree -upah /mnt/vps/var/lib/caddy/.local/share/caddy/certificates/
[drwxr--r-- www      4.0K]  /mnt/vps/var/lib/caddy/.local/share/caddy/certificates/
└── [drwxr--r-- www      4.0K]  acme-v02.api.letsencrypt.org-directory
    ├── [drwxr--r-- www      4.0K]  birb.it
    │   ├── [-rwxr--r-- www      5.2K]  birb.it.crt
    │   ├── [-rwxr--r-- www       146]  birb.it.json
    │   └── [-rwxr--r-- www       227]  birb.it.key
    ├── [drwxr--r-- www      4.0K]  dropzone.ingwie.me
    │   ├── [-rwxr--r-- www      5.2K]  dropzone.ingwie.me.crt
    │   ├── [-rwxr--r-- www       157]  dropzone.ingwie.me.json
    │   └── [-rwxr--r-- www       227]  dropzone.ingwie.me.key
    ├── [drwxr--r-- www      4.0K]  echo.birb.it
    │   ├── [-rwxr--r-- www      5.2K]  echo.birb.it.crt
    │   ├── [-rwxr--r-- www       151]  echo.birb.it.json
    │   └── [-rwxr--r-- www       227]  echo.birb.it.key
    ├── [drwxr--r-- www      4.0K]  gotify.ingwie.me
    │   ├── [-rwxr--r-- www      5.2K]  gotify.ingwie.me.crt
    │   ├── [-rwxr--r-- www       155]  gotify.ingwie.me.json
    │   └── [-rwxr--r-- www       227]  gotify.ingwie.me.key
    ├── [drwxr--r-- www      4.0K]  grocy.birb.it
    │   ├── [-rwxr--r-- www      5.2K]  grocy.birb.it.crt
    │   ├── [-rwxr--r-- www       152]  grocy.birb.it.json
    │   └── [-rwxr--r-- www       227]  grocy.birb.it.key
    ├── [drwxr--r-- www      4.0K]  ingwie.me
    │   ├── [-rwxr--r-- www      5.2K]  ingwie.me.crt
    │   ├── [-rwxr--r-- www       148]  ingwie.me.json
    │   └── [-rwxr--r-- www       227]  ingwie.me.key
    └── [drwxr--r-- www      4.0K]  video.birb.it
        ├── [-rwxr--r-- www      5.2K]  video.birb.it.crt
        ├── [-rwxr--r-- www       152]  video.birb.it.json
        └── [-rwxr--r-- www       227]  video.birb.it.key

I had changed the permissions so everyone could at least read - thinking that this would give the Caddy on the local server the proper permissions.

But it says “custom selection logic”. Is there maybe something else I must configure in my globals?

This is them right now:

# Globals
{
        debug true
        log {
                level debug
                output file /var/log/caddy.log {
                        roll_size 10mb
                        roll_keep 10
                        roll_keep_for 720h
                }
        }
        storage file_system /mnt/vps/var/lib/caddy/.local/share/caddy
        auto_https disable_certs
}

I had this spontaneous idea to make my main server’s Redis server available to the VPN, so that I could just share certs using that - completely working around the file system and permission collisions due to SSHFS. And, using the redis storage module via xcaddy, I can see that both servers are in fact connected and the entries are in fact populated accordingly.

I used this:

# On router -> VPN -> Server
  storage redis {
    host 192.168.222.10 # Server IP in VPN
    key_prefix caddy_
  }

And, I can see all the keys as well:

root@FriendlyWrt ~# redis-cli -h 192.168.222.10 -p 6379 --scan | grep birb
caddy_/certificates/acme-v02.api.letsencrypt.org-directory/grocy.birb.it/grocy.birb.it.key
caddy_/ocsp/echo.birb.it-ecd8c76f
caddy_/certificates/acme-v02.api.letsencrypt.org-directory/echo.birb.it/echo.birb.it.crt
caddy_/ocsp/grocy.birb.it-957f4c2c
caddy_/certificates/acme-v02.api.letsencrypt.org-directory/birb.it/birb.it.crt
caddy_/certificates/acme-v02.api.letsencrypt.org-directory/echo.birb.it/echo.birb.it.key
caddy_/certificates/acme-v02.api.letsencrypt.org-directory/echo.birb.it/echo.birb.it.json
caddy_/issue_cert_*.birb.it.lock
caddy_/certificates/acme-v02.api.letsencrypt.org-directory/grocy.birb.it/grocy.birb.it.crt
caddy_/ocsp/birb.it-20b7a5ed
caddy_/certificates/acme-v02.api.letsencrypt.org-directory/grocy.birb.it/grocy.birb.it.json
caddy_/certificates/acme-v02.api.letsencrypt.org-directory/birb.it/birb.it.json
caddy_/certificates/acme-v02.api.letsencrypt.org-directory/birb.it/birb.it.key

But despite all of that:

2022/12/14 17:40:40.312 debug   events  event   {"name": "tls_get_certificate", "id": "82f62cc3-157e-40d5-97c8-f872c4a20f15", "origin": "tls", "data": {"client_hello":{"CipherSuites":[52392,52393,52394,49196,49200,159,49188,49192,107,49162,49172,57,49195,49199,158,49187,49191,103,49161,49171,51,157,61,53,156,60,47,255],"ServerName":"echo.birb.it","SupportedCurves":[24,23,22,29],"SupportedPoints":"AA==","SignatureSchemes":[1539,1537,1283,1281,1027,1025,771,769],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[771,770,769],"Conn":{}}}}
2022/12/14 17:40:40.313 debug   tls.handshake   no matching certificates and no custom selection logic  {"identifier": "echo.birb.it"}
2022/12/14 17:40:40.313 debug   tls.handshake   no matching certificates and no custom selection logic  {"identifier": "*.birb.it"}
2022/12/14 17:40:40.313 debug   tls.handshake   no matching certificates and no custom selection logic  {"identifier": "*.*.it"}
2022/12/14 17:40:40.313 debug   tls.handshake   no matching certificates and no custom selection logic  {"identifier": "*.*.*"}
2022/12/14 17:40:40.313 debug   tls.handshake   all external certificate managers yielded no certificates and no errors {"remote_ip": "192.168.2.1", "remote_port": "38112", "sni": "echo.birb.it"}
2022/12/14 17:40:40.313 debug   tls.handshake   no certificate matching TLS ClientHello {"remote_ip": "192.168.2.1", "remote_port": "38112", "server_name": "echo.birb.it", "remote": "192.168.2.1:38112", "identifier": "echo.birb.it", "cipher_suites": [52392, 52393, 52394, 49196, 49200, 159, 49188, 49192, 107, 49162, 49172, 57, 49195, 49199, 158, 49187, 49191, 103, 49161, 49171, 51, 157, 61, 53, 156, 60, 47, 255], "cert_cache_fill": 0, "load_if_necessary": true, "obtain_if_necessary": true, "on_demand": false}
2022/12/14 17:40:40.313 debug   http.stdlib     http: TLS handshake error from 192.168.2.1:38112: no certificate available for 'echo.birb.it'

Am I overlooking something? This should’ve absolutely worked…

Just to make sure I’m clear on things, what is the config of the Caddy instance that is reporting “no certificate available”? Is it the exact same as the one posted above in the OP?

I figured it out! auto_https disable_certs actually disabled cert lookup.

After commenting it out, it works exactly as expected. I had understood that disable_certs would only disable auto-renew capabilities for SSL certificates. But apparently it also disabled lookup.

After turning it off, it looked up my certs properly and precisely as I had intended. And, now via Redis, there is no longer a need to manage file system permissions. You can see it working when you curl https://echo.birb.it. No more error!

Well, on my local network anyway. When I request the same URL from outside, I still get 502’d.

root@drachennetz:~# curl -v https://echo.birb.it
*   Trying 185.185.127.216:443...
* TCP_NODELAY set
* Connected to echo.birb.it (185.185.127.216) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=echo.birb.it
*  start date: Dec 14 16:27:21 2022 GMT
*  expire date: Mar 14 16:27:20 2023 GMT
*  subjectAltName: host "echo.birb.it" matched cert's "echo.birb.it"
*  issuer: C=US; O=Let's Encrypt; CN=R3
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x562fa9fec210)
> GET / HTTP/2
> Host: echo.birb.it
> user-agent: curl/7.68.0
> accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 502
< alt-svc: h3=":443"; ma=2592000
< server: Caddy
< content-length: 0
< date: Wed, 14 Dec 2022 18:03:15 GMT
<
* Connection #0 to host echo.birb.it left intact

Just to clarify, here are my configs:

# Router:
# Globals
{
        debug true
        log {
                level debug
                output file /var/log/caddy.log {
                        roll_size 10mb
                        roll_keep 10
                        roll_keep_for 720h
                }
        }
        #storage file_system /mnt/vps/var/lib/caddy/.local/share/caddy
        storage redis {
                host 192.168.222.10
                key_prefix caddy_
        }
        #auto_https disable_certs
}

(php) {
        php_fastcgi unix//var/run/php8-fpm.sock {
                capture_stderr true
                trusted_proxies 192.168.222.0/24
        }
}

# Basic ping-pong
echo.birb.it {
        respond "router here!"
}

# LuCi
router.birb.it:80 {
        reverse_proxy * localhost:8080
}

# Home Assistant
hass.birb.it:80 {
        reverse_proxy * localhost:8123 {
                trusted_proxies 192.168.222.0/24
        }
}

# TVHeadend
tvh.birb.it:80 {
        reverse_proxy * localhost:9981 {
                header_up X-Real-IP {remote_host}
                header_up X-Forwarded-For {remote_host}
        }
}

# Jellyfin
video.birb.it:80 {
        reverse_proxy * localhost:8096 {
                trusted_proxies 192.168.222.0/24
        }
}

# Navidrome
audio.birb.it:80 {
        reverse_proxy * localhost:4533 {
                trusted_proxies 192.168.222.0/24
        }
}

# Photoprism...?
# photo.birb.it:80 { reverse_proxy * localhost:x }

# NextCloud
cloud.birb.it:80 {
        root * /sdcard/srv/nextcloud/
        import php
        file_server
        redir /.well-known/carddav /remote.php/dav 301
        redir /.well-known/caldav /remote.php/dav 301
        redir /.well-known/webfinger /index.php/.well-known/webfinger 301
        redir /.well-known/nodeinfo /index.php/.well-known/nodeinfo 301
        @forbidden {
                path /.htaccess /data/* /config/* /db_structure /.xml /README /3rdparty/* /lib/* /templates/* /occ /console.php
        }
        respond @forbidden 404
}

# Monica
monica.birb.it:80 {
        root * /sdcard/srv/monica/public
        import php
        file_server
}

# Grocy
grocy.birb.it:80 {
        root * /sdcard/srv/grocy/public
        import php
        file_server
        #trusted_proxies 192.168.222.0/24
}

# Paperless / Paperspace
# paper.birb.it:80 { reverse_proxy * localhost:x }

tubesync.birb.it:80 {
        reverse_proxy * localhost:4848 {
                trusted_proxies 192.168.222.0/24
        }
}

neko.birb.it:80 {
        reverse_proxy * localhost:8989 {
                trusted_proxies 192.168.222.0/24
        }
}

rclone.birb.it:80 {
        root * /www/rclone-webui-react/
        #file_server
}
# VPS, main server:
{
        storage redis {
                host 192.168.222.10
                key_prefix caddy_
        }
}

birb.it {
        root * /srv/birb.it
        file_server
}

dropzone.ingwie.me {
        root * /mnt/rclone/wasabi/dropzone
        file_server
}

ingwie.me {
        # Matrix:
        #reverse_proxy /_matrix/* localhost:8008
        handle /_matrix/* {
                reverse_proxy localhost:8008
        }
        handle /.well-known/matrix/server {
                header Content-Type application/json
                header Access-Control-Allow-Origin *
                respond `{"m.server": "ingwie.me:443"}`
        }

        handle /.well-known/matrix/client {
                header Content-Type application/json
                header Access-Control-Allow-Origin *
                respond `{"m.homeserver": {"base_url": "https://ingwie.me:443"}}`
        }

        # Mastodon:
        handle /* {
                root * /srv/mastodon/public
                encode gzip
                @static file
                handle @static {
                        file_server
                }

                handle /api/v1/streaming* {
                        reverse_proxy localhost:4000
                }

                handle * {
                        reverse_proxy localhost:3000
                }

                header {
                        Strict-Transport-Security "max-age=31536000;"
                }

                header /sw.js Cache-Control "public, max-age=0";
                header /emoji* Cache-Control "public, max-age=31536000, immutable"
                header /packs* Cache-Control "public, max-age=31536000, immutable"
                header /system/accounts/avatars* Cache-Control "public, max-age=31536000, immutable"
                header /system/media_attachments/files* Cache-Control "public, max-age=31536000, immutable"
        }
        handle_errors {
                @5xx expression `{http.error.status_code} >= 500 && {http.error.status_code} < 600`
                rewrite @5xx /500.html
                file_server
        }
}

gotify.ingwie.me {
        reverse_proxy * localhost:9990
}

# Home router config
# Supposed to match non-exposed
*.birb.it {
        respond "...o.o..."
}

grocy.birb.it {
        reverse_proxy * 192.168.222.11:80
}
echo.birb.it {
        reverse_proxy * https://192.168.222.11
}

So on the local network, SSL works exactly as intended! But somehow, the reverse proxy… does not.

Almost there!

I checked my main server’s logs. Never knew grep did streams!

Anyway:


Dec 14 12:22:53 birb.it caddy[927688]: {"level":"debug","ts":1671042173.6718638,"logger":"events","msg":"event","name":"tls_get_certificate","id":"b4e25dda-af89-4fc0-938a-ee332e6edb16","origin":"tls","data":{"client_hello":{"CipherSuites":[4866,4867,4865,49196,49200,159,52393,52392,52394,49195,49199,158,49188,49192,107,49187,49191,103,49162,49172,57,49161,49171,51,157,156,61,60,53,47,255],"ServerName":"echo.birb.it","SupportedCurves":[29,23,30,25,24],"SupportedPoints":"AAEC","SignatureSchemes":[1027,1283,1539,2055,2056,2057,2058,2059,2052,2053,2054,1025,1281,1537,771,769,770,1026,1282,1538],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[772,771],"Conn":{}}}}
Dec 14 12:22:53 birb.it caddy[927688]: {"level":"debug","ts":1671042173.6720066,"logger":"tls.handshake","msg":"choosing certificate","identifier":"echo.birb.it","num_choices":1}
Dec 14 12:22:53 birb.it caddy[927688]: {"level":"debug","ts":1671042173.6720362,"logger":"tls.handshake","msg":"default certificate selection results","identifier":"echo.birb.it","subjects":["echo.birb.it"],"managed":true,"issuer_key":"acme-v02.api.letsencrypt.org-directory","hash":"056551b9456d2e4369181c2587818254837476564b7e09bd0b377e44e0bdb2e5"}
Dec 14 12:22:53 birb.it caddy[927688]: {"level":"debug","ts":1671042173.6720524,"logger":"tls.handshake","msg":"matched certificate in cache","remote_ip":"91.247.228.166","remote_port":"60298","subjects":["echo.birb.it"],"managed":true,"expiration":1678811241,"hash":"056551b9456d2e4369181c2587818254837476564b7e09bd0b377e44e0bdb2e5"}
Dec 14 12:22:53 birb.it caddy[927688]: {"level":"debug","ts":1671042173.7770495,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"192.168.222.11:443","duration":0.075891505,"request":{"remote_ip":"91.247.228.166","remote_port":"60298","proto":"HTTP/2.0","method":"GET","host":"echo.birb.it","uri":"/","headers":{"User-Agent":["curl/7.68.0"],"Accept":["*/*"],"X-Forwarded-For":["91.247.228.166"],"X-Forwarded-Proto":["https"],"X-Forwarded-Host":["echo.birb.it"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"echo.birb.it"}},"error":"remote error: tls: internal error"}
Dec 14 12:22:53 birb.it caddy[927688]: {"level":"debug","ts":1671042173.7771733,"logger":"http.log.error.log0","msg":"remote error: tls: internal error","request":{"remote_ip":"91.247.228.166","remote_port":"60298","proto":"HTTP/2.0","method":"GET","host":"echo.birb.it","uri":"/","headers":{"User-Agent":["curl/7.68.0"],"Accept":["*/*"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"echo.birb.it"}},"duration":0.076129597,"status":502,"err_id":"9urwrtmd2","err_trace":"reverseproxy.statusError (reverseproxy.go:1272)"}
Dec 14 12:22:53 birb.it caddy[927688]: {"level":"error","ts":1671042173.7771947,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"91.247.228.166","remote_port":"60298","proto":"HTTP/2.0","method":"GET","host":"echo.birb.it","uri":"/","headers":{"User-Agent":["curl/7.68.0"],"Accept":["*/*"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"echo.birb.it"}},"user_id":"","duration":0.076129597,"size":0,"status":502,"resp_headers":{"Alt-Svc":["h3=\":443\"; ma=2592000"],"Server":["Caddy"]}}

So the SSL certs works in my local network (192.168.2.0/24) - but when my main server makes a request over the VPN (192.168.222.0/24), it fails. Odd, because:

root@birb:~# curl --resolve echo.birb.it:443:192.168.222.11 -Lv https://echo.birb.it; echo
* Added echo.birb.it:443:192.168.222.11 to DNS cache
* Hostname echo.birb.it was found in DNS cache
*   Trying 192.168.222.11:443...
* Connected to echo.birb.it (192.168.222.11) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=echo.birb.it
*  start date: Dec 14 16:27:21 2022 GMT
*  expire date: Mar 14 16:27:20 2023 GMT
*  subjectAltName: host "echo.birb.it" matched cert's "echo.birb.it"
*  issuer: C=US; O=Let's Encrypt; CN=R3
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x5567c78612c0)
> GET / HTTP/2
> Host: echo.birb.it
> user-agent: curl/7.74.0
> accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 200
< alt-svc: h3=":443"; ma=2592000
< content-type: text/plain; charset=utf-8
< server: Caddy
< content-length: 12
< date: Wed, 14 Dec 2022 18:28:16 GMT
<
* Connection #0 to host echo.birb.it left intact
router here!

So this works exactly as exected. But any time I go through my main server, it fails. Any ideas?

I let this sit for a while and did other things just to come back to this with a refreshed mindset. And, I found the answer! IT WORKS!!

Short story:

echo.birb.it {
  reverse_proxy * https://192.168.222.11 {
    transport http {
      tls_server_name echo.birb.it
    }
  }
}

Long story:
My initial idea was, that maybe there was an issue verifying the SSL cert in the first place. So, I disabled it first with tls_skip_verify. But while I was there in those docs, I read something peculiar - I’ve marked the passage I mean.

tls_server_name sets the server name used when verifying the certificate received in the TLS handshake. By default, this will <use the upstream address’ host part>.

But, in my config, I use https://192.168.222.11 - so, what is being used to verify the certificate is this IP - NOT echo.birb.it!

This tiny detail changed it, and i am very, very happy about this. Now I just have to find a ay to basically automate importing this…

What is the placeholder for the host being configured? I am going to go and find out, assuming it’s {host}. But, this also means I now have a fully secured network and can use the same certs inside and outside the network, synchronize them via Redis, and selectively set things up to forward and use a wildcard domain to catch anything I don’t want proxied and do something else with it instead.

This sure was an adventure, but I am done, it works and I couldn’t be happier :smiley:

To summarize:

  • Using the Redis storage, I have storage redis configured to point at the upstream server with the prefix settings synchronized so that both instances see the same certificates.
  • auto_https is untouched, so cert management works - although I am a little worried about it, I will just hope it works out.
  • tls_server_name is the key to mapping the reverse proxy’s IP to the actual hostname used.

Thank you for this awesome piece of software! But honestly, this verification error should definitively have popped up in the logs and it did not. Maybe a limitation of the SSL lib? I don’t know. But it very much should cause an error message in the logs instead with details about what exactly failed to make tracking and fixing this error easier.

Have a great day!

2 Likes

Yeah, it should be fine. Either of them can initiate cert automation, and the one that can solve the challenges (i.e. the public one) will catch the ACME challenge requests.

Yeah, it’s necessary to override so that the upstream selects the correct certificate.

The clue in this case probably should’ve been that TLS certificate selection in debug logs (of your upstream server) was using the IP address instead of the domain.

I’m not sure why your front server was only logging this though:

Go stdlib ideally should be more specific about the error. It’s possible that at the Caddy layer we’re not doing enough to reveal what the error means. But I’m not sure.

Yeah, that gives you the value of the Host header of the current request, essentially.

If you mean of the configured upstream, then {upstream_hostport} would give you that. See the docs on that topic: reverse_proxy (Caddyfile directive) — Caddy Documentation

2 Likes

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