1. The problem I’m having:
Caddy is running inside docker as a file_server
. When connecting to it (e.g. using curl), I get an SSL error:
$ curl -vL https://172.30.96.152/
* Uses proxy env variable no_proxy == 'localhost,127.0.0.1,::1,.some-host.local,.some-host.de,172.16.0.0/12'
* Trying 172.30.96.152:443...
* Connected to 172.30.96.152 (172.30.96.152) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/ssl/certs/ca-certificates.crt
* CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS alert, internal error (592):
* OpenSSL/3.0.13: error:0A000438:SSL routines::tlsv1 alert internal error
* Closing connection
curl: (35) OpenSSL/3.0.13: error:0A000438:SSL routines::tlsv1 alert internal error
- The command is executed from the docker host.
- 172.30.96.152 is the ip of this machine in the local network (not the docker network), I’m using this ip instead of localhost, since the certificate was issued for it and I want to test that.
- I installed the root certificate on this machine:
sudo docker compose cp php:/data/caddy/pki/authorities/local/root.crt /usr/local/share/ca-certificates/root.crt && sudo update-ca-certificates
- I also verified, that
/etc/ssl/certs/ca-certificates.crt
contains this certificat.
I guess this has something to do with caddy running inside docker, since in the logs there is something like no certificate available for '10.20.0.2'
and 10.20.0.2
is the ip of the container in the docker network. However I want to have the certificate for the hosts ip, since that is of course what the clients in my local network will use. I guess my understanding of how this should work with Caddy is just not sufficient.
Note: The docker setup originally stems from https://github.com/dunglas/symfony-docker
which uses frankenphp under the hood, which is a caddy configuration for php, as much as I understand it. The current configuration however just uses caddy as fileserver.
2. Error messages and/or full log output:
Waiting for database to be ready...
The database is now ready and reachable
[OK] Already at the latest version ("DoctrineMigrations\Version20241115093811")
2025/02/06 09:46:45.708 INFO using config from file {"file": "/etc/caddy/Caddyfile"}
2025/02/06 09:46:45.710 INFO adapted config to JSON {"adapter": "caddyfile"}
2025/02/06 09:46:45.710 WARN Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies {"adapter": "caddyfile", "file": "/etc/caddy/Caddyfile", "line": 2}
2025/02/06 09:46:45.711 INFO admin admin endpoint started {"address": "localhost:2019", "enforce_origin": false, "origins": ["//[::1]:2019", "//127.0.0.1:2019", "//localhost:2019"]}
2025/02/06 09:46:45.712 INFO tls.cache.maintenance started background certificate maintenance {"cache": "0xc0007d1380"}
2025/02/06 09:46:45.712 INFO http.auto_https 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}
2025/02/06 09:46:45.712 INFO http.auto_https enabling automatic HTTP->HTTPS redirects {"server_name": "srv0"}
2025/02/06 09:46:45.712 DEBUG http.auto_https adjusted config {"tls": {"automation":{"policies":[{"subjects":["172.30.96.152"]},{}]}}, "http": {"servers":{"remaining_auto_https_redirects":{"listen":[":80"],"routes":[{},{}]},"srv0":{"listen":[":443"],"routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"vars","root":"/app/frontend/WebContent"},{"encodings":{"br":{},"gzip":{},"zstd":{}},"handler":"encode","prefer":["zstd","br","gzip"]},{"handler":"file_server","hide":["/etc/caddy/Caddyfile"]}]}]}],"terminal":true}],"tls_connection_policies":[{}],"automatic_https":{}}}}}
2025/02/06 09:46:45.713 WARN pki.ca.local installing root certificate (you might be prompted for password) {"path": "storage:pki/authorities/local/root.crt"}
2025/02/06 09:46:45.714 INFO warning: "certutil" is not available, install "certutil" with "apt install libnss3-tools" or "yum install nss-tools" and try again
2025/02/06 09:46:45.714 INFO define JAVA_HOME environment variable to use the Java trust
2025/02/06 09:46:46.343 INFO certificate installed properly in linux trusts
2025/02/06 09:46:46.349 INFO tls storage cleaning happened too recently; skipping for now {"storage": "FileStorage:/data/caddy", "instance": "3c8bc581-0b32-4eae-b0c6-2a42a9b7ab49", "try_again": "2025/02/07 09:46:46.349", "try_again_in": 86399.999999088}
2025/02/06 09:46:46.349 INFO tls finished cleaning storage units
2025/02/06 09:46:46.360 DEBUG setHandler
2025/02/06 09:46:46.360 DEBUG setHandler
2025/02/06 09:46:46.360 DEBUG setHandler
2025/02/06 09:46:46.360 DEBUG setHandler
2025/02/06 09:46:46.360 INFO FrankenPHP started 🐘 {"php_version": "8.3.16", "num_threads": 4}
2025/02/06 09:46:46.360 DEBUG http starting server loop {"address": "[::]:443", "tls": true, "http3": false}
2025/02/06 09:46:46.360 INFO http enabling HTTP/3 listener {"addr": ":443"}
2025/02/06 09:46:46.361 INFO 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.
2025/02/06 09:46:46.361 INFO http.log server running {"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2025/02/06 09:46:46.361 DEBUG http starting server loop {"address": "[::]:80", "tls": false, "http3": false}
2025/02/06 09:46:46.361 WARN http HTTP/2 skipped because it requires TLS {"network": "tcp", "addr": ":80"}
2025/02/06 09:46:46.361 WARN http HTTP/3 skipped because it requires TLS {"network": "tcp", "addr": ":80"}
2025/02/06 09:46:46.361 INFO http.log server running {"name": "remaining_auto_https_redirects", "protocols": ["h1", "h2", "h3"]}
2025/02/06 09:46:46.361 INFO http enabling automatic TLS certificate management {"domains": ["172.30.96.152"]}
2025/02/06 09:46:46.361 DEBUG tls.cache added certificate to cache {"subjects": ["172.30.96.152"], "expiration": "2025/02/06 20:19:02.000", "managed": true, "issuer_key": "local", "hash": "045c85a0f6904604cf5f59c8a77f74522818f104ca5ebe78a0967f7458096742", "cache_size": 1, "cache_capacity": 10000}
2025/02/06 09:46:46.361 DEBUG events event {"name": "cached_managed_cert", "id": "f0e83406-2f7b-4992-aec0-72e9caf72681", "origin": "tls", "data": {"sans":["172.30.96.152"]}}
2025/02/06 09:46:46.361 INFO autosaved config (load with --resume flag) {"file": "/config/caddy/autosave.json"}
2025/02/06 09:46:46.362 INFO serving initial configuration
2025/02/06 09:46:46.362 INFO watcher watching config file for changes {"config_file": "/etc/caddy/Caddyfile"}
2025/02/06 09:46:50.295 DEBUG admin.api received request {"method": "GET", "host": "localhost:2019", "uri": "/metrics", "remote_ip": "127.0.0.1", "remote_port": "48950", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}}
2025/02/06 09:47:20.345 DEBUG admin.api received request {"method": "GET", "host": "localhost:2019", "uri": "/metrics", "remote_ip": "127.0.0.1", "remote_port": "43244", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}}
2025/02/06 09:47:50.395 DEBUG admin.api received request {"method": "GET", "host": "localhost:2019", "uri": "/metrics", "remote_ip": "127.0.0.1", "remote_port": "47730", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}}
2025/02/06 09:48:20.449 DEBUG admin.api received request {"method": "GET", "host": "localhost:2019", "uri": "/metrics", "remote_ip": "127.0.0.1", "remote_port": "45342", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}}
2025/02/06 09:48:50.503 DEBUG admin.api received request {"method": "GET", "host": "localhost:2019", "uri": "/metrics", "remote_ip": "127.0.0.1", "remote_port": "47420", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}}
2025/02/06 09:49:20.552 DEBUG admin.api received request {"method": "GET", "host": "localhost:2019", "uri": "/metrics", "remote_ip": "127.0.0.1", "remote_port": "56206", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}}
2025/02/06 09:49:50.609 DEBUG admin.api received request {"method": "GET", "host": "localhost:2019", "uri": "/metrics", "remote_ip": "127.0.0.1", "remote_port": "49260", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}}
2025/02/06 09:50:20.667 DEBUG admin.api received request {"method": "GET", "host": "localhost:2019", "uri": "/metrics", "remote_ip": "127.0.0.1", "remote_port": "45410", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}}
2025/02/06 09:50:50.722 DEBUG admin.api received request {"method": "GET", "host": "localhost:2019", "uri": "/metrics", "remote_ip": "127.0.0.1", "remote_port": "36446", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}}
2025/02/06 09:51:20.774 DEBUG admin.api received request {"method": "GET", "host": "localhost:2019", "uri": "/metrics", "remote_ip": "127.0.0.1", "remote_port": "33640", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}}
2025/02/06 09:51:50.825 DEBUG admin.api received request {"method": "GET", "host": "localhost:2019", "uri": "/metrics", "remote_ip": "127.0.0.1", "remote_port": "33390", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}}
2025/02/06 09:52:20.883 DEBUG admin.api received request {"method": "GET", "host": "localhost:2019", "uri": "/metrics", "remote_ip": "127.0.0.1", "remote_port": "50692", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}}
2025/02/06 09:52:50.941 DEBUG admin.api received request {"method": "GET", "host": "localhost:2019", "uri": "/metrics", "remote_ip": "127.0.0.1", "remote_port": "59746", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}}
2025/02/06 09:53:20.998 DEBUG admin.api received request {"method": "GET", "host": "localhost:2019", "uri": "/metrics", "remote_ip": "127.0.0.1", "remote_port": "58630", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}}
2025/02/06 09:53:51.055 DEBUG admin.api received request {"method": "GET", "host": "localhost:2019", "uri": "/metrics", "remote_ip": "127.0.0.1", "remote_port": "51788", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.88.1"]}}
2025/02/06 09:53:56.641 DEBUG events event {"name": "tls_get_certificate", "id": "822f4bf5-0e3b-487b-ad1f-2404c5144051", "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":"","SupportedCurves":[29,23,30,25,24,256,257,258,259,260],"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],"RemoteAddr":{"IP":"172.30.96.152","Port":38834,"Zone":""},"LocalAddr":{"IP":"10.20.0.2","Port":443,"Zone":""}}}}
2025/02/06 09:53:56.641 DEBUG tls.handshake no matching certificates and no custom selection logic {"identifier": "10.20.0.2"}
2025/02/06 09:53:56.641 DEBUG tls.handshake no certificate matching TLS ClientHello {"remote_ip": "172.30.96.152", "remote_port": "38834", "server_name": "", "remote": "172.30.96.152:38834", "identifier": "10.20.0.2", "cipher_suites": [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], "cert_cache_fill": 0.0001, "load_or_obtain_if_necessary": true, "on_demand": false}
2025/02/06 09:53:56.641 DEBUG http.stdlib http: TLS handshake error from 172.30.96.152:38834: no certificate available for '10.20.0.2'
3. Caddy version:
I didn’t find out, where caddy was installed in the docker container. However the output of frankenphp --version
is:
FrankenPHP v1.4.1 PHP 8.3.16 Caddy v2.9.1 h1:OEYiZ7DbCzAWVb6TNEkjRcSCRGHVoZsJinoDR/n9oaY=
4. How I installed and ran Caddy:
a. System environment:
Inside docker
# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
On the host
# $ cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.1 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
b. Command:
I don’t know how frankenphp works and how it starts caddy. If necessary I will dig deeper.
c. Service/unit/compose file:
services:
php:
container_name: $DASHBOARD_PHP_APP_CONTAINER_NAME
image: $DOCKER_IMAGE_REPOSITORY/php:$APP_VERSION
restart: unless-stopped
environment:
SERVER_NAME: ${SERVER_NAME:-localhost}, php:80
MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
# Run "composer require symfony/orm-pack" to install and configure Doctrine ORM
DATABASE_URL: $MARIADB_URL
# Run "composer require symfony/mercure-bundle" to install and configure the Mercure integration
MERCURE_URL: ${CADDY_MERCURE_URL:-http://php/.well-known/mercure}
MERCURE_PUBLIC_URL: ${CADDY_MERCURE_PUBLIC_URL:-https://${SERVER_NAME:-localhost}/.well-known/mercure}
MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
# The two next lines can be removed after initial installation
SYMFONY_VERSION: ${SYMFONY_VERSION:-}
STABILITY: ${STABILITY:-stable}
volumes:
#- ./volumes/caddy/data:/data
#- ./volumes/caddy/config:/config
- caddy_data:/data
- caddy_config:/config
#- ./volumes/var:/app/var
- app_var:/app/var
- ./frankenphp/docker-entrypoint.sh:/usr/local/bin/docker-entrypoint
- vendor:/app/vendor
ports:
# HTTP
- target: 80
published: ${HTTP_PORT:-80}
protocol: tcp
# HTTPS
- target: 443
published: ${HTTPS_PORT:-443}
protocol: tcp
# HTTP/3
- target: 443
published: ${HTTP3_PORT:-443}
protocol: udp
# Websocket
- "3001:3001"
# Mercure is installed as a Caddy module, prevent the Flex recipe from installing another service
###> symfony/mercure-bundle ###
###< symfony/mercure-bundle ###
###> doctrine/doctrine-bundle ###
database:
image: mariadb:$MARIADB_IMAGE
container_name: $DATABASE_CONTAINER_NAME
environment:
MARIADB_DATABASE: $MARIADB_DATABASE
MARIADB_ROOT_PASSWORD: $MARIADB_ROOT_PASSWORD
MARIADB_USER: $MARIADB_USER
MARIADB_PASSWORD: $MARIADB_PASSWORD
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
timeout: 5s
retries: 10
ports:
- "3306:3306"
volumes:
- database_data:/var/lib/mysql/data:rw
- ./migrations/initial-sql/:/docker-entrypoint-initdb.d/
###< doctrine/doctrine-bundle ###
volumes:
caddy_data:
caddy_config:
database_data:
app_log:
app_var:
vendor:
compose.override.yaml
services:
php:
build:
context: .
target: frankenphp_dev
volumes:
- ./:/app
- ./frankenphp/Caddyfile:/etc/caddy/Caddyfile:ro
- ./frankenphp/conf.d/20-app.dev.ini:/usr/local/etc/php/app.conf.d/20-app.dev.ini:ro
# If you develop on Mac or Windows you can remove the vendor/ directory
# from the bind-mount for better performance by enabling the next line:
#- /app/vendor
environment:
MERCURE_EXTRA_DIRECTIVES: demo
# See https://xdebug.org/docs/all_settings#mode
XDEBUG_MODE: "${XDEBUG_MODE:-off}"
extra_hosts:
# Ensure that host.docker.internal is correctly defined on Linux
- host.docker.internal:host-gateway
tty: true
###> symfony/mercure-bundle ###
###< symfony/mercure-bundle ###
###> doctrine/doctrine-bundle ###
database:
ports:
- "5432"
###< doctrine/doctrine-bundle ###
d. My complete Caddy config:
{
debug
frankenphp {
{$FRANKENPHP_CONFIG}
}
}
{$CADDY_EXTRA_CONFIG}
(symfony) {
root * /app/public
encode zstd br gzip
mercure {
# Transport to use (default to Bolt)
transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
# Publisher JWT key
publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
# Subscriber JWT key
subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
# Allow anonymous subscribers (double-check that it's what you want)
anonymous
# Enable the subscription API (double-check that it's what you want)
subscriptions
# Extra directives
{$MERCURE_EXTRA_DIRECTIVES}
}
vulcain
{$CADDY_SERVER_EXTRA_DIRECTIVES}
# Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics
header ?Permissions-Policy "browsing-topics=()"
php_server
}
https://172.30.96.152:443 {
tls internal
root /app/frontend/WebContent
encode zstd br gzip
file_server
}