Reverse proxy via paths only shows a white page (but the title works)

1. The problem I’m having:

Trying to run a reverse proxy via handle_path leads to a white page, but the Title-tag is shown, so caddy at least gets some response. Using a subdomain works, but is not what I want.
The curl output further down is correct though, path and subdomain give the same result.

The proxied service is a docker container:

services:
tools:
image: corentinth/it-tools:latest
ports:
- 8000:80

2. Error messages and/or full log output:

> caddy run -w
2026/02/26 21:39:57.592	INFO	maxprocs: Leaving GOMAXPROCS=16: CPU quota undefined
2026/02/26 21:39:57.592	INFO	GOMEMLIMIT is updated	{"GOMEMLIMIT": 46308114432, "previous": 9223372036854775807}
2026/02/26 21:39:57.592	INFO	using adjacent Caddyfile
2026/02/26 21:39:57.592	INFO	adapted config to JSON	{"adapter": "caddyfile"}
2026/02/26 21:39:57.604	INFO	admin	admin endpoint started	{"address": "localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2026/02/26 21:39:57.604	INFO	tls.cache.maintenance	started background certificate maintenance	{"cache": "0x34fdf1345280"}
2026/02/26 21:39:57.604	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}
2026/02/26 21:39:57.604	INFO	http.auto_https	enabling automatic HTTP->HTTPS redirects	{"server_name": "srv0"}
2026/02/26 21:39:57.606	DEBUG	http.auto_https	adjusted config	{"tls": {"automation":{"policies":[{"subjects":["tools.localhost","localhost"]},{}]}}, "http": {"servers":{"remaining_auto_https_redirects":{"listen":[":80"],"routes":[{},{}]},"srv0":{"listen":[":443"],"routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"localhost:8000"}]}]}]}],"terminal":true},{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"rewrite","strip_path_prefix":"/tools"}]},{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"localhost:8000"}]}]}]}],"match":[{"path":["/tools/*"]}]}]}],"terminal":true}],"tls_connection_policies":[{}],"automatic_https":{}}}}}
2026/02/26 21:39:57.619	INFO	tls	storage cleaning happened too recently; skipping for now	{"storage": "FileStorage:C:\\Users\\dustb\\AppData\\Roaming\\Caddy", "instance": "34045383-a7eb-4caf-96e9-35c91437047b", "try_again": "2026/02/27 21:39:57.619", "try_again_in": 86400}
2026/02/26 21:39:57.619	INFO	tls	finished cleaning storage units
2026/02/26 21:39:57.620	INFO	pki.ca.local	root certificate is already trusted by system	{"path": "storage:pki/authorities/local/root.crt"}
2026/02/26 21:39:57.621	DEBUG	http	starting server loop	{"address": "[::]:443", "tls": true, "http3": false}
2026/02/26 21:39:57.621	INFO	http	enabling HTTP/3 listener	{"addr": ":443"}
2026/02/26 21:39:57.621	INFO	http.log	server running	{"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2026/02/26 21:39:57.621	DEBUG	http	starting server loop	{"address": "[::]:80", "tls": false, "http3": false}
2026/02/26 21:39:57.621	WARN	http	HTTP/2 skipped because it requires TLS	{"network": "tcp", "addr": ":80"}
2026/02/26 21:39:57.621	WARN	http	HTTP/3 skipped because it requires TLS	{"network": "tcp", "addr": ":80"}
2026/02/26 21:39:57.621	INFO	http.log	server running	{"name": "remaining_auto_https_redirects", "protocols": ["h1", "h2", "h3"]}
2026/02/26 21:39:57.621	INFO	http	enabling automatic TLS certificate management	{"domains": ["tools.localhost", "localhost"]}
2026/02/26 21:39:57.622	WARN	tls	stapling OCSP	{"identifiers": ["tools.localhost"]}
2026/02/26 21:39:57.622	DEBUG	tls.cache	added certificate to cache	{"subjects": ["tools.localhost"], "expiration": "2026/02/27 09:22:03.000", "managed": true, "issuer_key": "local", "hash": "9771157e0c6c91f75a324b6999612b74f6bd47783dcad94e2b8972e31aca8df8", "cache_size": 1, "cache_capacity": 10000}
2026/02/26 21:39:57.622	DEBUG	events	event	{"name": "cached_managed_cert", "id": "a276fc39-ab10-4d6b-a0b3-8ef63ea36a96", "origin": "tls", "data": {"sans":["tools.localhost"]}}
2026/02/26 21:39:57.622	WARN	tls	stapling OCSP	{"identifiers": ["localhost"]}
2026/02/26 21:39:57.623	DEBUG	tls.cache	added certificate to cache	{"subjects": ["localhost"], "expiration": "2026/02/27 06:06:27.000", "managed": true, "issuer_key": "local", "hash": "77c2a6805bfd86ed97f24c90e409b17c49aa9d2acb9410e82b7c0071c2182778", "cache_size": 2, "cache_capacity": 10000}
2026/02/26 21:39:57.623	DEBUG	events	event	{"name": "cached_managed_cert", "id": "b3254d5f-529b-4944-963d-49931f34c033", "origin": "tls", "data": {"sans":["localhost"]}}
2026/02/26 21:39:57.623	DEBUG	events	event	{"name": "started", "id": "467a79f1-fab0-4a9a-affa-206318ada342", "origin": "", "data": null}
2026/02/26 21:39:57.623	INFO	autosaved config (load with --resume flag)	{"file": "C:\\Users\\dustb\\AppData\\Roaming\\Caddy\\autosave.json"}
2026/02/26 21:39:57.623	INFO	serving initial configuration
2026/02/26 21:39:57.623	INFO	watcher	watching config file for changes	{"config_file": "Caddyfile"}
2026/02/26 21:40:01.165	DEBUG	events	event	{"name": "tls_get_certificate", "id": "147e7dc0-df80-4014-9915-4d0a5e62803b", "origin": "tls", "data": {"client_hello":{"CipherSuites":[47802,4865,4866,4867,49195,49199,49196,49200,52393,52392,49171,49172,156,157,47,53],"ServerName":"localhost","SupportedCurves":[2570,4588,29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[1027,2052,1025,1283,2053,1281,2054,1537],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[2570,772,771],"RemoteAddr":{"IP":"::1","Port":60137,"Zone":""},"LocalAddr":{"IP":"::1","Port":443,"Zone":""}}}}
2026/02/26 21:40:01.165	DEBUG	tls.handshake	choosing certificate	{"identifier": "localhost", "num_choices": 1}
2026/02/26 21:40:01.166	DEBUG	tls.handshake	default certificate selection results	{"identifier": "localhost", "subjects": ["localhost"], "managed": true, "issuer_key": "local", "hash": "77c2a6805bfd86ed97f24c90e409b17c49aa9d2acb9410e82b7c0071c2182778"}
2026/02/26 21:40:01.166	DEBUG	tls.handshake	matched certificate in cache	{"remote_ip": "::1", "remote_port": "60137", "subjects": ["localhost"], "managed": true, "expiration": "2026/02/27 06:06:27.000", "hash": "77c2a6805bfd86ed97f24c90e409b17c49aa9d2acb9410e82b7c0071c2182778"}
2026/02/26 21:40:01.168	DEBUG	http.handlers.rewrite	rewrote request	{"request": {"remote_ip": "::1", "remote_port": "60137", "client_ip": "::1", "proto": "HTTP/2.0", "method": "GET", "host": "localhost", "uri": "/", "headers": {"Sec-Fetch-Dest": ["document"], "Upgrade-Insecure-Requests": ["1"], "User-Agent": ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36"], "Sec-Fetch-Mode": ["navigate"], "Sec-Fetch-User": ["?1"], "Accept-Encoding": ["gzip, deflate, br, zstd"], "Accept-Language": ["en-US,en;q=0.9,de-DE;q=0.8,de;q=0.7"], "Priority": ["u=0, i"], "Cache-Control": ["max-age=0"], "Sec-Ch-Ua-Platform": ["\"Windows\""], "Dnt": ["1"], "Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"], "Sec-Fetch-Site": ["none"], "Cookie": ["REDACTED"], "If-None-Match": ["\"67176216-ae3\""], "If-Modified-Since": ["Tue, 22 Oct 2024 08:28:06 GMT"], "Sec-Ch-Ua": ["\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\", \"Google Chrome\";v=\"144\""], "Sec-Ch-Ua-Mobile": ["?0"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "h2", "server_name": "localhost", "ech": false}}, "method": "GET", "uri": "/"}
2026/02/26 21:40:01.168	DEBUG	http.handlers.reverse_proxy	selected upstream	{"dial": "localhost:8000", "total_upstreams": 1}
2026/02/26 21:40:01.172	DEBUG	http.handlers.reverse_proxy	upstream roundtrip	{"upstream": "localhost:8000", "duration": 0.0037794, "request": {"remote_ip": "::1", "remote_port": "60137", "client_ip": "::1", "proto": "HTTP/2.0", "method": "GET", "host": "localhost", "uri": "/", "headers": {"Sec-Ch-Ua": ["\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\", \"Google Chrome\";v=\"144\""], "Via": ["2.0 Caddy"], "Priority": ["u=0, i"], "Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"], "If-Modified-Since": ["Tue, 22 Oct 2024 08:28:06 GMT"], "X-Forwarded-Proto": ["https"], "Accept-Encoding": ["gzip, deflate, br, zstd"], "Accept-Language": ["en-US,en;q=0.9,de-DE;q=0.8,de;q=0.7"], "Cache-Control": ["max-age=0"], "Sec-Ch-Ua-Platform": ["\"Windows\""], "If-None-Match": ["\"67176216-ae3\""], "Sec-Ch-Ua-Mobile": ["?0"], "X-Forwarded-For": ["::1"], "User-Agent": ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36"], "Sec-Fetch-Mode": ["navigate"], "Dnt": ["1"], "Sec-Fetch-Site": ["none"], "Cookie": ["REDACTED"], "Sec-Fetch-Dest": ["document"], "X-Forwarded-Host": ["localhost"], "Upgrade-Insecure-Requests": ["1"], "Sec-Fetch-User": ["?1"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "h2", "server_name": "localhost", "ech": false}}, "headers": {"Date": ["Thu, 26 Feb 2026 21:40:01 GMT"], "Last-Modified": ["Tue, 22 Oct 2024 08:28:06 GMT"], "Connection": ["keep-alive"], "Etag": ["\"67176216-ae3\""], "Server": ["nginx/1.26.2"]}, "status": 304}
2026/02/26 21:40:01.321	DEBUG	events	event	{"name": "tls_get_certificate", "id": "40b5f926-ae57-422a-bbaa-1376f216f1de", "origin": "tls", "data": {"client_hello":{"CipherSuites":[23130,4865,4866,4867,49195,49199,49196,49200,52393,52392,49171,49172,156,157,47,53],"ServerName":"localhost","SupportedCurves":[56026,4588,29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[1027,2052,1025,1283,2053,1281,2054,1537],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[2570,772,771],"RemoteAddr":{"IP":"::1","Port":64784,"Zone":""},"LocalAddr":{"IP":"::1","Port":443,"Zone":""}}}}
2026/02/26 21:40:01.321	DEBUG	tls.handshake	choosing certificate	{"identifier": "localhost", "num_choices": 1}
2026/02/26 21:40:01.321	DEBUG	tls.handshake	default certificate selection results	{"identifier": "localhost", "subjects": ["localhost"], "managed": true, "issuer_key": "local", "hash": "77c2a6805bfd86ed97f24c90e409b17c49aa9d2acb9410e82b7c0071c2182778"}
2026/02/26 21:40:01.321	DEBUG	tls.handshake	matched certificate in cache	{"remote_ip": "::1", "remote_port": "64784", "subjects": ["localhost"], "managed": true, "expiration": "2026/02/27 06:06:27.000", "hash": "77c2a6805bfd86ed97f24c90e409b17c49aa9d2acb9410e82b7c0071c2182778"}


> curl.exe -kvL localhost/tools/
* Host localhost:80 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:80...
* Established connection to localhost (::1 port 80) from ::1 port 62038
* using HTTP/1.x
> GET /tools/ HTTP/1.1
> Host: localhost
> User-Agent: curl/8.16.0
> Accept: */*
>
< HTTP/1.1 308 Permanent Redirect
< Connection: close
< Location: https://localhost/tools/
< Server: Caddy
< Date: Thu, 26 Feb 2026 21:47:40 GMT
< Content-Length: 0
<
* shutting down connection #0
* Clear auth, redirects to port from 80 to 443
* Issue another request to this URL: 'https://localhost/tools/'
* Host localhost:443 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:443...
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* ALPN: server accepted http/1.1
* Established connection to localhost (::1 port 443) from ::1 port 62039
* using HTTP/1.x
> GET /tools/ HTTP/1.1
> Host: localhost
> User-Agent: curl/8.16.0
> Accept: */*
>
* schannel: remote party requests renegotiation
* schannel: renegotiating SSL/TLS connection
* schannel: SSL/TLS connection renegotiated
* Request completely sent off
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Alt-Svc: h3=":443"; ma=2592000
< Content-Length: 2787
< Content-Type: text/html
< Date: Thu, 26 Feb 2026 21:47:40 GMT
< Etag: "67176216-ae3"
< Last-Modified: Tue, 22 Oct 2024 08:28:06 GMT
< Server: nginx/1.26.2
< Via: 1.1 Caddy
<
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>IT Tools - Handy online tools for developers</title>
    <meta itemprop="name" content="IT Tools - Handy online tools for developers" />
    <meta
      name="description"
      content="Collection of handy online tools for developers, with great UX. IT Tools is a free and open-source collection of handy online tools for developers & people working in IT."
    />
    <meta
      itemprop="description"
      content="Collection of handy online tools for developers, with great UX. IT Tools is a free and open-source collection of handy online tools for developers & people working in IT."
    />
    <link rel="author" href="humans.txt" />
    <link rel="canonical" href="https://it-tools.tech" />

    <link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png" />
    <link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png" />
    <link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png" />
    <link rel="mask-icon" href="safari-pinned-tab.svg" color="#18a058" />
    <meta name="msapplication-TileColor" content="#da532c" />
    <meta name="theme-color" content="#ffffff" />

    <meta property="og:url" content="https://it-tools.tech/" />
    <meta property="og:type" content="website" />
    <meta property="og:title" content="IT Tools - Handy online tools for developers" />
    <meta
      property="og:description"
      content="Collection of handy online tools for developers, with great UX. IT Tools is a free and open-source collection of handy online tools for developers & people working in IT."
    />
    <meta property="og:image" content="https://it-tools.tech/banner.png?v=2" />

    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:site" content="@ittoolsdottech" />
    <meta name="twitter:creator" content="@cthmsst" />

    <meta name="twitter:title" content="IT Tools - Handy online tools for developers" />
    <meta
      name="twitter:description"
      content="Collection of handy online tools for developers, with great UX. IT Tools is a free and open-source collection of handy online tools for developers & people working in IT."
    />
    <meta name="twitter:image" content="https://it-tools.tech/banner.png?v=2" />
    <meta name="twitter:image:alt" content="IT Tools - Handy online tools for developers" />
    <script type="module" crossorigin src="/assets/index-f8ba620c.js"></script>
    <link rel="stylesheet" href="/assets/index-f0f4dd3b.css">
  <link rel="manifest" href="/manifest.webmanifest"></head>
  <body>
    <div id="app"></div>

  </body>
</html>
* Connection #1 to host localhost:443 left intact

3. Caddy version:

v2.11.1 h1:C7sQpsFOC5CH+31KqJc7EoOf8mXrOEkFyYd6GpIqm/s=

4. How I installed and ran Caddy:

winget add caddy

a. System environment:

Win11

b. Command:

caddy run -w

c. Service/unit/compose file:

d. My complete Caddy config:

{
	debug
}

localhost {
	handle_path /tools/* {
		reverse_proxy localhost:8000
	}
}

tools.localhost {
	reverse_proxy localhost:8000
}

5. Links to relevant resources:

See this article

You’re stripping /tools off of the path passed to your app, but your app tries to load JS files from /assets which is not being proxied, so those files get blank responses and cannot load the content of your page.

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