Can't seem to make reverse proxy work with domain(s)

1. The problem I’m having:

For almost a month I’m trying to debug my Caddyfile configs and try to make reverse proxy work. Due to my constant failures I’ve realized I will try the getting started guide and make everything much more simple. I currently use a very simple Caddyfile to simulate the most basic funcionality of reverse proxying an app/service. It did work with a port passing, but my goal was to do so with a domain, which never worked for me. Only the default “Caddy is working” web and the static Web file. But never once i’ve managed making reverse proxy working with a domain.

Test with curl:

❯ curl -vL qbittorrent.toubul.eu:8080
* Host qbittorrent.toubul.eu:8080 was resolved.
* IPv6: (none)
* IPv4: 10.0.0.5
*   Trying 10.0.0.5:8080...
* Connected to qbittorrent.toubul.eu (10.0.0.5) port 8080
* using HTTP/1.x
> GET / HTTP/1.1
> Host: qbittorrent.toubul.eu:8080
> User-Agent: curl/8.12.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 308 Permanent Redirect
< Connection: close
< Location: https://qbittorrent.toubul.eu/
< Server: Caddy
< Date: Thu, 03 Apr 2025 18:50:54 GMT
< Content-Length: 0
<
* shutting down connection #0
* Clear auth, redirects to port from 8080 to 443
* Issue another request to this URL: 'https://qbittorrent.toubul.eu/'
* Host qbittorrent.toubul.eu:443 was resolved.
* IPv6: (none)
* IPv4: 10.0.0.5
*   Trying 10.0.0.5:443...
* connect to 10.0.0.5 port 443 from 172.16.0.2 port 47574 failed: Connection refused
* Failed to connect to qbittorrent.toubul.eu port 443 after 259 ms: Could not connect to server
* closing connection #1
curl: (7) Failed to connect to qbittorrent.toubul.eu port 443 after 259 ms: Could not connect to server

2. Error messages and/or full log output:

{"level":"info","ts":1743706232.2686718,"logger":"admin.api","msg":"received request","method":"POST","host":"localhost:2019","uri":"/stop","remote_ip":"127.0.0.1","remote_port":"39386","headers":{"Accept-Encoding":["gzip"],"Content-Length":["0"],"Origin":["http://localhost:2019"],"User-Agent":["Go-http-client/1.1"]}}
{"level":"warn","ts":1743706232.268712,"logger":"admin.api","msg":"exiting; byeee!! 👋"}
{"level":"info","ts":1743706232.2687309,"logger":"http","msg":"servers shutting down with eternal grace period"}
{"level":"info","ts":1743706232.2691922,"logger":"admin","msg":"stopped previous server","address":"localhost:2019"}
{"level":"info","ts":1743706232.2693276,"logger":"admin.api","msg":"shutdown complete","exit_code":0}
{"level":"info","ts":1743706233.320389,"msg":"using config from file","file":"/config/Caddyfile"}
{"level":"info","ts":1743706233.3221493,"msg":"adapted config to JSON","adapter":"caddyfile"}
{"level":"info","ts":1743706233.3350906,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//localhost:2019","//[::1]:2019","//127.0.0.1:2019"]}
{"level":"info","ts":1743706233.3353515,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc0006aba80"}
{"level":"info","ts":1743706233.3356676,"logger":"http.auto_https","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv0","https_port":8443}
{"level":"info","ts":1743706233.3356788,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
{"level":"debug","ts":1743706233.3356974,"logger":"http.auto_https","msg":"adjusted config","tls":{"automation":{"policies":[{"subjects":["*.toubul.eu"]},{}]}},"http":{"http_port":8080,"https_port":8443,"servers":{"remaining_auto_https_redirects":{"listen":[":8080"],"routes":[{},{}]},"srv0":{"listen":[":8443"],"routes":[{"handle":[{"handler":"subroute","routes":[{"group":"group2","handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"vars","root":"/app/www"},{"handler":"file_server","hide":["/config/Caddyfile"]}]}]}],"match":[{"host":["caddy.toubul.eu"]}]},{"group":"group2","handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","transport":{"protocol":"http","tls":{"insecure_skip_verify":true}},"upstreams":[{"dial":"qbittorrent:8080"}]}]}]}],"match":[{"host":["qbittorrent.toubul.eu"]}]}]}],"terminal":true}],"tls_connection_policies":[{}],"automatic_https":{}}}}}
{"level":"debug","ts":1743706233.3360527,"logger":"http","msg":"starting server loop","address":"[::]:8443","tls":true,"http3":false}
{"level":"info","ts":1743706233.3360803,"logger":"http","msg":"enabling HTTP/3 listener","addr":":8443"}
{"level":"info","ts":1743706233.336217,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
{"level":"debug","ts":1743706233.3362548,"logger":"http","msg":"starting server loop","address":"[::]:8080","tls":false,"http3":false}
{"level":"warn","ts":1743706233.336261,"logger":"http","msg":"HTTP/2 skipped because it requires TLS","network":"tcp","addr":":8080"}
{"level":"warn","ts":1743706233.3362648,"logger":"http","msg":"HTTP/3 skipped because it requires TLS","network":"tcp","addr":":8080"}
{"level":"info","ts":1743706233.3362682,"logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}
{"level":"info","ts":1743706233.3362718,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["*.toubul.eu"]}
{"level":"debug","ts":1743706233.3381793,"logger":"tls.cache","msg":"added certificate to cache","subjects":["*.toubul.eu"],"expiration":1751415644,"managed":true,"issuer_key":"acme-v02.api.letsencrypt.org-directory","hash":"e75730dcfba33f995cc8e0813ca1c2dcc72bebf0926973f8479ea4ff1ec08319","cache_size":1,"cache_capacity":10000}
{"level":"debug","ts":1743706233.3381982,"logger":"events","msg":"event","name":"cached_managed_cert","id":"4ef7acf6-a54e-4933-81e8-3af765a42931","origin":"tls","data":{"sans":["*.toubul.eu"]}}
{"level":"info","ts":1743706233.3386621,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}
{"level":"info","ts":1743706233.3386683,"msg":"serving initial configuration"}
{"level":"info","ts":1743706233.3455331,"logger":"tls","msg":"storage cleaning happened too recently; skipping for now","storage":"FileStorage:/config/caddy","instance":"223acb42-54f5-436b-a6c2-d48aabb8a338","try_again":1743792633.345531,"try_again_in":86399.999999529}
{"level":"info","ts":1743706233.3459973,"logger":"tls","msg":"finished cleaning storage units"}

3. Caddy version: 2.9.1

4. How I installed and ran Caddy:

Hotio Docker image on an Unraid Server app install.

a. System environment:

Docker, Unraid 7.0.1

b. Command:

/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker create --name='caddy' --net='proxy' --pids-limit 2048 -e TZ="Asia/Jerusalem" -e HOST_OS="Unraid" -e HOST_HOSTNAME="Sunny" -e HOST_CONTAINERNAME="caddy" -e 'CUSTOM_BUILD'='' -e 'CLOUDFLARE_EMAIL'='${CF_EMAIL}' -e 'CLOUDFLARE_API_TOKEN'='${CF_API_TOKEN}' -e 'PRIVOXY_ENABLED'='false' -e 'UNBOUND_ENABLED'='false' -e 'VPN_ENABLED'='false' -e 'VPN_CONF'='wg0' -e 'VPN_PROVIDER'='generic' -e 'VPN_LAN_NETWORK'='' -e 'VPN_EXPOSE_PORTS_ON_LAN'='' -e 'VPN_AUTO_PORT_FORWARD'='true' -e 'VPN_AUTO_PORT_FORWARD_TO_PORTS'='' -e 'VPN_FIREWALL_TYPE'='auto' -e 'VPN_PIA_USER'='' -e 'VPN_PIA_PASS'='' -e 'VPN_PIA_PREFERRED_REGION'='' -e 'VPN_PIA_DIP_TOKEN'='no' -e 'VPN_PIA_PORT_FORWARD_PERSIST'='false' -e 'PUID'='99' -e 'PGID'='100' -e 'UMASK'='002' -l net.unraid.docker.managed=dockerman -l net.unraid.docker.webui='http://[IP]:[PORT:8080]' -l net.unraid.docker.icon='https://hotio.dev/webhook-avatars/caddy.png' -p '8080:8080/tcp' -p '8443:8443/tcp' -v '/mnt/user/appdata/caddy':'/config':'rw' --env-file /mnt/user/appdata/caddy/.env --hostname=caddy.internal --cap-add=NET_ADMIN --sysctl="net.ipv4.conf.all.src_valid_mark=1" --sysctl="net.ipv6.conf.all.disable_ipv6=1" 'ghcr.io/hotio/caddy:release' 

d. My complete Caddy config:

{
	email {env.CF_EMAIL}
	http_port 8080
	https_port 8443
	debug
}

(block_world) {
	@block not remote_ip private_ranges
	abort @block
}

(log_settings) {
	log {
		output file /config/access.log
		level DEBUG
	}
}

*.toubul.eu {
	tls {
		dns cloudflare {env.CF_API_TOKEN}
		propagation_delay 1m
		resolvers 1.1.1.1 1.0.0.1
	}

	@caddy host caddy.toubul.eu
	handle @caddy {
		## import log_settings
		root * /app/www
		file_server
		## import block_world
	}

	@qbittorrent host qbittorrent.toubul.eu
	handle @qbittorrent {
		reverse_proxy https://qbittorrent:8080 {
			transport http {
				tls
				tls_insecure_skip_verify
			}
		}
	}
}

5. Links to relevant resources:

Caddy keeps all managed certificates renewed and redirects HTTP (default port 80 ) to HTTPS (default port 443 ) automatically.

https_port doesn’t mean that the redirect is supposed to go to port 8443. Automatic HTTPS always redirects to port 443. https_port only means that the specified non-standard port number is HTTPS.

My quick example how to address it:

{
	http_port 8080
	https_port 8443
}

http://localhost {
    redir https://localhost:8443 308
}

localhost {
	respond "HTTPS Alive"
}

Test:

$ curl -vL http://localhost:8080
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
* using HTTP/1.x
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.12.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 308 Permanent Redirect
< Location: https://localhost:8443
< Server: Caddy
< Date: Thu, 03 Apr 2025 20:15:39 GMT
< Content-Length: 0
* Ignoring the response-body
* setting size while ignoring
<
* Connection #0 to host localhost left intact
* Clear auth, redirects to port from 8080 to 8443
* Issue another request to this URL: 'https://localhost:8443/'
* Host localhost:8443 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
* 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 / x25519 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
*  subject:
*  start date: Apr  3 20:01:30 2025 GMT
*  expire date: Apr  4 08:01:30 2025 GMT
*  subjectAltName: host "localhost" matched cert's "localhost"
*  issuer: CN=Caddy Local Authority - ECC Intermediate
*  SSL certificate verify ok.
*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
*   Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
*   Certificate level 2: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* Connected to localhost (::1) port 8443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://localhost:8443/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: localhost:8443]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.12.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: localhost:8443
> User-Agent: curl/8.12.1
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Request completely sent off
< HTTP/2 200
< alt-svc: h3=":8443"; ma=2592000
< content-type: text/plain; charset=utf-8
< server: Caddy
< content-length: 11
< date: Thu, 03 Apr 2025 20:15:39 GMT
<
* Connection #1 to host localhost left intact
HTTPS Alive

Add this to your Caddyfile:

http://qbittorrent.toubul.eu {
   redir https://qbittorrent.toubul.eu:8443 308
}
1 Like

If I understand what you mean, you are saying that going to port 8080 is wrong as it is only http.
But, I also tried making a request to the 8443 port.

❯ curl -vL qbittorrent.toubul.eu:8443
* Host qbittorrent.toubul.eu:8443 was resolved.
* IPv6: (none)
* IPv4: 10.0.0.5
*   Trying 10.0.0.5:8443...
* Connected to qbittorrent.toubul.eu (10.0.0.5) port 8443
* using HTTP/1.x
> GET / HTTP/1.1
> Host: qbittorrent.toubul.eu:8443
> User-Agent: curl/8.12.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

note that I get a TLS handshake error on the logs of the container:

{"level":"debug","ts":1743715410.772849,"logger":"http.stdlib","msg":"http: TLS handshake error from 172.16.0.2:37704: client sent an HTTP request to an HTTPS server"}

I tried the redir for testing, altough im not sure i understood your solution.
I’ve got an error:
Error: Caddyfile:45: unrecognized directive: http://qbittorrent.toubul.eu

for:

*caddyfile contents from above...*

	@qbittorrent host qbittorrent.toubul.eu
	handle @qbittorrent {
		reverse_proxy https://qbittorrent:8080 {
			transport http {
				tls
				tls_insecure_skip_verify
			}
		}
	}

	http://qbittorrent.toubul.eu {
		redir https://qbittorrent.toubul.eu:8443 308
	}

No, this is not an HTTPS request. What you’re doing here is you’re sending an HTTP request to HTTPS port.

If you want HTTP, go to http://qbittorrent.toubul.eu:8080.
If you want HTTPS, go to https://qbittorrent.toubul.eu:8443.

That is wrong. You can’t add that into another virtual. What I was hoping you’d do was:

## your other config stuff

*.toubul.eu {
	## your *.toubul.eu config stuff
}

http://qbittorrent.toubul.eu {
	redir https://qbittorrent.toubul.eu:8443 308
}

Please compare your code with my localhost example.

1 Like

Just thought I would add that if you want to make it work for all your *.toubul.eu, you might want to try:

## your other config stuff

*.toubul.eu {
	## your *.toubul.eu config stuff
}

http://*.toubul.eu {
	redir https://{host}:8443 308
}

Thank you for the brief response, I have edited my response too late so you probably missed it. (also, sadly there is a timer for 1 hour between replies - at least for me.)
Thanks for letting me know about the http prefix compared to https and that the port isn’t enough.
That said, I’m getting HTTP ERROR 502 and TLS handshake error on the HTTPS request.
That is the main issue that won’t allow me to do anything. There were always issues that come and go, but my main issue was mostly a TLS handshake error.
That is the logs and on the browser.
on curl it is :

❯ curl -vL https://qbittorrent.toubul.eu:8443
* Host qbittorrent.toubul.eu:8443 was resolved.
* IPv6: (none)
* IPv4: 10.0.0.5
*   Trying 10.0.0.5:8443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
* 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 / x25519 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=*.toubul.eu
*  start date: Apr  3 00:20:44 2025 GMT
*  expire date: Jul  2 00:20:43 2025 GMT
*  subjectAltName: host "qbittorrent.toubul.eu" matched cert's "*.toubul.eu"
*  issuer: C=US; O=Let's Encrypt; CN=E5
*  SSL certificate verify ok.
*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA384
*   Certificate level 1: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to qbittorrent.toubul.eu (10.0.0.5) port 8443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://qbittorrent.toubul.eu:8443/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: qbittorrent.toubul.eu:8443]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.12.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: qbittorrent.toubul.eu:8443
> User-Agent: curl/8.12.1
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Request completely sent off
< HTTP/2 502
< alt-svc: h3=":8443"; ma=2592000
< server: Caddy
< content-length: 0
< date: Thu, 03 Apr 2025 23:21:15 GMT
<
* Connection #0 to host qbittorrent.toubul.eu left intact

log from caddy log:

{"level":"error","ts":1743722225.246138,"logger":"http.log.error","msg":"EOF","request":{"remote_ip":"172.16.0.2","remote_port":"60510","client_ip":"172.16.0.2","proto":"HTTP/2.0","method":"GET","host":"qbittorrent.toubul.eu:8443","uri":"/favicon.ico","headers":{"Sec-Fetch-Mode":["no-cors"],"Sec-Fetch-Site":["same-origin"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Dnt":["1"],"Referer":["https://qbittorrent.toubul.eu:8443/"],"Sec-Gpc":["1"],"Sec-Fetch-Dest":["image"],"Priority":["u=6"],"Accept":["image/avif,image/jxl,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"qbittorrent.toubul.eu"}},"duration":7.321875089,"status":502,"err_id":"m09iqnz56","err_trace":"reverseproxy.statusError (reverseproxy.go:1373)"}

I can see the HTTP 502, but I’m not spotting the TLS handshake error - could you highlight that part for me, in case I’m overlooking it?

The HTTP 502 usually means Caddy can’t reach your back-end server.

Just to double-check - are you sure your qbittorrent instance on port 8080 is using https? It’s definitely possible, but 8080 is more commonly used for http, so I just want to rule out a misconfiguration that could be causing Caddy to throw that 502.

Here’s the part I’m referring to:

		reverse_proxy https://qbittorrent:8080 {
			transport http {
				tls
				tls_insecure_skip_verify
			}
		}

If qbittorrent isn’t actually using https, then you’ll want to change that to:

		reverse_proxy http://qbittorrent:8080

Let’s check that first.

If that doesn’t solve it, I’ve got one more theory about your 502 - just need a bit more context. Could you post how you’re running your caddy and qbittorrent? Your docker command if you’re using the CLI, or your docker-compose.yaml if you’re using docker-compose.