Caddy & Tailscale - reverse proxy issues with immich, teslamate and grafana

1. The problem I’m having:

Hi everyone, I’m trying to setup reverse proxy for immich with caddy and tailscale.

Tailscale is hosted on “host” network.
Caddy, portainer, immich, teslamate, grafana are hosted on a “bridge” network.

Facing issues with hosted applications on docker containers.

  1. If i remove tls_insecure_skip_verify from Caddyfile, it throws "400 Bad Request
    The plain HTTP request was sent to HTTPS port".
    Why is this happening only with grafana and immich? Portainer and teslamate are working fine without this.
  2. After adding the tls_insecure_skip_verify, getting a blank page for immich and grafana with error in Caddy logs “status”:502,“err_trace”:"reverseproxy.statusError (reverseproxy.go:1267).

2. Error messages and/or full log output:

Grafana

{"level":"debug","ts":1712973737.5665598,"logger":"events","msg":"event","name":"tls_get_certificate","id":"ecc86f01-1eca-43cc-b5dc-23c5ec7a421c","origin":"tls","data":{"client_hello":{"CipherSuites":[4865,4867,4866,49195,49199,52393,52392,49196,49200,49162,49161,49171,49172,156,157,47,53],"ServerName":"synology-mytailscale.ts.net","SupportedCurves":[29,23,24,25,256,257],"SupportedPoints":"AA==","SignatureSchemes":[1027,1283,1539,2052,2053,2054,1025,1281,1537,515,513],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[772,771],"RemoteAddr":{"IP":"172.17.0.1","Port":58270,"Zone":""},"LocalAddr":{"IP":"172.17.0.12","Port":443,"Zone":""}}}}
{"level":"debug","ts":1712973737.5666752,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"synology-mytailscale.ts.net"}
{"level":"debug","ts":1712973737.5666854,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.chipmunk-tailor.ts.net"}
{"level":"debug","ts":1712973737.5666902,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.ts.net"}
{"level":"debug","ts":1712973737.5666947,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.*.net"}
{"level":"debug","ts":1712973737.566699,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.*.*"}
{"level":"debug","ts":1712973737.569988,"logger":"tls.handshake","msg":"using externally-managed certificate","remote_ip":"172.17.0.1","remote_port":"58270","sni":"synology-mytailscale.ts.net","names":["synology-mytailscale.ts.net"],"expiration":1720459124}
{"level":"debug","ts":1712973737.5781484,"logger":"http.handlers.rewrite","msg":"rewrote request","request":{"remote_ip":"172.17.0.1","remote_port":"58270","client_ip":"172.17.0.1","proto":"HTTP/2.0","method":"GET","host":"synology-mytailscale.ts.net","uri":"/grafana","headers":{"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Site":["none"],"Sec-Fetch-User":["?1"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Upgrade-Insecure-Requests":["1"],"Sec-Gpc":["1"],"Accept-Language":["en-US,en;q=0.5"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0"],"Cookie":[],"Dnt":["1"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"synology-mytailscale.ts.net"}},"method":"GET","uri":"/"}
{"level":"debug","ts":1712973737.5781956,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"192.168.0.33:3000","total_upstreams":1}
{"level":"debug","ts":1712973737.5792289,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"192.168.0.33:3000","duration":0.000963199,"request":{"remote_ip":"172.17.0.1","remote_port":"58270","client_ip":"172.17.0.1","proto":"HTTP/2.0","method":"GET","host":"synology-mytailscale.ts.net","uri":"/","headers":{"Sec-Fetch-Dest":["document"],"X-Forwarded-Proto":["https"],"X-Forwarded-Host":["synology-mytailscale.ts.net"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0"],"Accept-Encoding":["gzip, deflate, br"],"Accept-Language":["en-US,en;q=0.5"],"Dnt":["1"],"Cookie":[],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Site":["none"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Sec-Gpc":["1"],"Upgrade-Insecure-Requests":["1"],"X-Forwarded-For":["172.17.0.1"],"Sec-Fetch-User":["?1"],"Te":["trailers"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"synology-mytailscale.ts.net"}},"error":"tls: first record does not look like a TLS handshake"}
{"level":"error","ts":1712973737.579313,"logger":"http.log.error","msg":"tls: first record does not look like a TLS handshake","request":{"remote_ip":"172.17.0.1","remote_port":"58270","client_ip":"172.17.0.1","proto":"HTTP/2.0","method":"GET","host":"synology-mytailscale.ts.net","uri":"/grafana","headers":{"Cookie":[],"Dnt":["1"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Site":["none"],"Sec-Fetch-User":["?1"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Upgrade-Insecure-Requests":["1"],"Sec-Gpc":["1"],"Accept-Language":["en-US,en;q=0.5"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"synology-mytailscale.ts.net"}},"duration":0.001241482,"status":502,"err_id":"mt09dgcaz","err_trace":"reverseproxy.statusError (reverseproxy.go:1267)"}

immich

{"level":"debug","ts":1712973818.495334,"logger":"http.handlers.rewrite","msg":"rewrote request","request":{"remote_ip":"172.17.0.1","remote_port":"58270","client_ip":"172.17.0.1","proto":"HTTP/2.0","method":"GET","host":"synology-mytailscale.ts.net","uri":"/immich","headers":{"Cookie":[],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Site":["none"],"Dnt":["1"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-User":["?1"],"Sec-Gpc":["1"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Mode":["navigate"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"synology-mytailscale.ts.net"}},"method":"GET","uri":"/"}
{"level":"debug","ts":1712973818.4953842,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"192.168.0.33:2283","total_upstreams":1}
{"level":"debug","ts":1712973818.4966915,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"192.168.0.33:2283","duration":0.001243328,"request":{"remote_ip":"172.17.0.1","remote_port":"58270","client_ip":"172.17.0.1","proto":"HTTP/2.0","method":"GET","host":"synology-mytailscale.ts.net","uri":"/","headers":{"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Dest":["document"],"Sec-Gpc":["1"],"Sec-Fetch-User":["?1"],"Sec-Fetch-Site":["none"],"Accept-Language":["en-US,en;q=0.5"],"X-Forwarded-For":["172.17.0.1"],"X-Forwarded-Proto":["https"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0"],"Te":["trailers"],"Dnt":["1"],"X-Forwarded-Host":["synology-mytailscale.ts.net"],"Cookie":[],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Accept-Encoding":["gzip, deflate, br"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"synology-mytailscale.ts.net"}},"error":"tls: first record does not look like a TLS handshake"}
{"level":"error","ts":1712973818.4967837,"logger":"http.log.error","msg":"tls: first record does not look like a TLS handshake","request":{"remote_ip":"172.17.0.1","remote_port":"58270","client_ip":"172.17.0.1","proto":"HTTP/2.0","method":"GET","host":"synology-mytailscale.ts.net","uri":"/immich","headers":{"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Mode":["navigate"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0"],"Cookie":[],"Te":["trailers"],"Dnt":["1"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Site":["none"],"Sec-Gpc":["1"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-User":["?1"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"synology-mytailscale.ts.net"}},"duration":0.001514853,"status":502,"err_id":"pi7i6rc30","err_trace":"reverseproxy.statusError (reverseproxy.go:1267)"}

3. Caddy version:

CADDY_VERSION v2.7.6

4. How I installed and ran Caddy:

Installed on Portainer with docker compose

a. System environment:

Synology DSM 7.2.1
Portainer - Docker version 20.10.23

c. My complete Caddy docker compose file:

version: "3.9"

services:
  caddy:
    image: caddy:latest
    restart: unless-stopped
    container_name: caddy
    hostname: caddy
    network_mode: bridge

    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - /volume1/docker/caddy/Caddyfile:/etc/caddy/Caddyfile
      - /volume1/docker/caddy/data:/data
      - /volume1/docker/caddy/config:/config
       # tailscale creates its socket on /tmp, so we'll kidnap from there to expose to caddy
      - /volume1/docker/tailscale/tmp/tailscaled.sock:/var/run/tailscale/tailscaled.sock

d. My complete Caddy config:

{
	debug
}

(network_paths) {
	handle_path /portainer* {
		reverse_proxy 192.168.0.1:9000
# Hitting "https://synology-mytailscale.ts.net/portainer"
 # works fine, loads the portainer login page
	}
	handle_path /teslamate* {
		reverse_proxy 192.168.0.1:4000.   # teslamate is on port 4000:4000
# Hitting "https://synology.mytailscale.ts.net/teslamate" 
 # works fine, but its not rendering the teslamate webpage CSS properly
	}
	handle_path /grafana* {
		reverse_proxy 192.168.0.1:3000 {. # grafana is on port 3000:3000
			transport http {
				tls_insecure_skip_verify
			}
		}
	}
	handle_path /immich* {
		reverse_proxy 192.168.0.1:2283 {      # Immich is on port 2283:3001
			transport http {
				tls_insecure_skip_verify
			}
		}
	}
}

synology-mytailscale.ts.net {
	import network_paths
}

e. Teslamate docker composer file

version: "3.9"

name: teslamate
services:
  teslamate:
    hostname: teslamate
    image: teslamate/teslamate:latest
    container_name: teslamate
    restart: on-failure:5
    environment:
      - ENCRYPTION_KEY=fasldfjhweriuycxvzxdfwer
      - DATABASE_USER=teslamate
      - DATABASE_PASS=sdgljadhgweriuowvxvmn
      - DATABASE_NAME=teslamate
      - DATABASE_HOST=database
      - MQTT_HOST=mosquitto
    ports:
      - 4000:4000
    cap_drop:
      - all
    networks:
      - bridge

  database:
    image: postgres:15
    container_name: teslamate-db
    restart: on-failure:5
    environment:
      - POSTGRES_USER=teslamate
      - POSTGRES_PASSWORD=sdgljadhgweriuowvxvmn
      - POSTGRES_DB=teslamate
    volumes:
      - /volume1/docker/teslamate/teslamate-db:/var/lib/postgresql/data:rw
    networks:
      - bridge

  grafana:
    image: teslamate/grafana:latest
    container_name: teslamate-grafana
    restart: on-failure:5
    environment:
      - DATABASE_USER=teslamate
      - DATABASE_PASS=sdgljadhgweriuowvxvmn
      - DATABASE_NAME=teslamate
      - DATABASE_HOST=database
    ports:
      - 3000:3000
    volumes:
      - /volume1/docker/teslamate/teslamate-grafana:/var/lib/grafana:rw
    networks:
      - bridge

  mosquitto:
    image: eclipse-mosquitto:2
    container_name: teslamate-mosquitto
    restart: on-failure:5
    command: mosquitto -c /mosquitto-no-auth.conf
    # ports:
    #   - 1883:1883
    volumes:
      - /volume1/docker/teslamate/teslamate-mosquitto/config:/mosquitto/config:rw
      - /volume1/docker/teslamate/teslamate-mosquitto/data:/mosquitto/data:rw
      - /volume1/docker/teslamate/teslamate-mosquitto/log:/mosquitto/log:rw
    networks:
      - bridge

# Network definitions
networks:
  # Define the network
  bridge:
    # Use the bridge driver
    driver: bridge

f. Immich docker compose file

version: "3" # Use version 3 of the docker-compose file format

# Service definitions for the big-bear-immich application
services:
  # Main Immich Server service configuration
  immich-server:
    hostname: immich
    container_name: immich-server # Name of the running container
    image: ghcr.io/immich-app/immich-server:v1.101.0 # Image to be used
    command: ["start.sh", "immich"] # Command to be executed upon container start
    ports: # Mapping ports from the host OS to the container
      - 2283:3001
    volumes: # Mounting directories for persistent data storage
      - immich_upload:/usr/src/app/upload
      - /volume1/homes/lashchdh/Photos:/usr/src/app/external:ro
    environment: # Setting environment variables
      DB_HOSTNAME: immich-postgres
      DB_USERNAME: immich
      DB_PASSWORD: ghjdhfhwrqewrxcvb
      DB_DATABASE_NAME: immich
      DB_PORT: 5432
      REDIS_HOSTNAME: immich-redis
      IMMICH_MACHINE_LEARNING_URL: http://immich-machine-learning:3003
    depends_on: # Dependencies to ensure the order of service startup
      - redis
      - database
    restart: always # Policy to always restart the container if it stops
    networks:
      - bridge

  # Configuration for the Immich Microservices
  immich-microservices:
    container_name: immich-microservices # Name of the running container
    image: ghcr.io/immich-app/immich-server:v1.101.0 # Image to be used
    # extends:  # Feature to use configuration from another service (currently commented out)
    #   file: hwaccel.yml
    #   service: hwaccel
    command: ["start.sh", "microservices"] # Command to be executed upon container start
    volumes: # Mounting directories for persistent data storage
      - immich_upload:/usr/src/app/upload
      - /volume1/homes/lashchdh/Photos:/usr/src/app/external:ro
    environment: # Setting environment variables
      DB_HOSTNAME: immich-postgres
      DB_USERNAME: immich
      DB_PASSWORD: ghjdhfhwrqewrxcvb
      DB_DATABASE_NAME: immich
      DB_PORT: 5432
      REDIS_HOSTNAME: immich-redis
    depends_on: # Dependencies to ensure the order of service startup
      - redis
      - database
    restart: always # Policy to always restart the container if it stops
    networks:
      - bridge

  # Configuration for Immich Machine Learning service
  immich-machine-learning:
    container_name: immich-machine-learning # Name of the running container
    image: ghcr.io/immich-app/immich-machine-learning:v1.101.0 # Image to be used
    volumes: # Mounting directories for persistent data storage
      - immich_cache:/cache
    environment: # Setting environment variables
      DB_HOSTNAME: immich-postgres
      DB_USERNAME: immich
      DB_PASSWORD: ghjdhfhwrqewrxcvb
      DB_DATABASE_NAME: immich
      DB_PORT: 5432
      REDIS_HOSTNAME: immich-redis
    restart: always # Policy to always restart the container if it stops
    networks:
      - bridge

  # Configuration for Redis service
  redis:
    container_name: immich-redis # Name of the running container
    image: redis:6.2-alpine@sha256:70a7a5b641117670beae0d80658430853896b5ef269ccf00d1827427e3263fa3 # Image to be used
    restart: always # Policy to always restart the container if it stops
    networks:
      - bridge

  # Configuration for Database service
  database:
    container_name: immich-postgres # Name of the running container
    image: tensorchord/pgvecto-rs:pg14-v0.2.0 # Image to be used
    environment: # Setting environment variables
      POSTGRES_PASSWORD: ghjdhfhwrqewrxcvb
      POSTGRES_USER: immich
      POSTGRES_DB: immich
      PG_DATA: /var/lib/postgresql/data
    volumes: # Mounting directories for persistent data storage
      - immich_postgres:/var/lib/postgresql/data
    restart: always # Policy to always restart the container if it stops
    networks:
      - bridge

# Network definitions
networks:
  # Define the network
  bridge:
    # Use the bridge driver
    driver: bridge

# Define named volumes for data persistence.
volumes:
  # Define a named volume for Immich data.
  immich_cache:
    # Use the local storage driver.
    driver: local
  immich_upload:
    # Use the local storage driver.
    driver: local
  immich_postgres:
    # Use the local storage driver.
    driver: local

5. What I already tried:

Search online for posts with similar errors on caddy forums, reddit and github, tried everything. I either get a blank page, 400 or 502 errors.
Not sure what to try beyond these.

6. Links to relevant resources:

Noticed these errors from logs

“tls.handshake”,“msg”:“no matching certificates and no custom selection logic”
“tls: first record does not look like a TLS handshake”

Looks like the tailscale certificates are not working as expected, or the folder/files are inaccessible to the docker containers?? How to validate this?
Attaching my tailscale docker compose file for reference.

version: "3.9"

services:
  tailscale:
    container_name: tailscaled
    image: tailscale/tailscale
    network_mode: host
    cap_add:
      - NET_ADMIN
      - NET_RAW
    volumes:
      - /dev/net/tun:/dev/net/tun
      - /volume1/docker/tailscale/varlib:/var/lib
      - /volume1/docker/tailscale/tmp:/tmp
    environment:
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_HOSTNAME=synology-tailscale-docker
      - TS_SOCKET=/tmp/tailscaled.sock
      - TS_AUTH_KEY=tskey-auth-xxxxxxxxx

This doesn’t matter. You need to proxy to the port number internal to the docker container, not the one you bound to the host, when Caddy is running in Docker.

Use the container name of the service, not an IP address (so it uses the Docker container’s IP address, using Docker’s DNS resolver).

So for example, use reverse_proxy immich-server:3001 for example.

You can/should remove port mappings from those containers as well, so that they’re only accessible through Caddy, as your proxy. If you have their ports bound to the host, then any client that can connect directly to the host machine (assuming there’s no firewall involved) could skip past Caddy, potentially skipping past TLS or whatever auth config you may have in Caddy.

You should make sure to proxy to the HTTP port of each service, not the HTTPS port. And then you should remove the transport & tls_insecure_skip_verify stuff.

Thanks for the explanation, now it makes more sense.
I’ve narrowed to portainer and immich and incorporated your recommendations.
My test URLs are https://mytailscale.net/immich and /portainer. Now portainer proxy stopped working as well.

I’m getting “no matching certificates and no custom selection logic” error on first hit, and repeated hits throws error "“status”:502,“reverseproxy.statusError (reverseproxy.go:1267)”.

Looks like caddy isn’t reading the tailscale certificates as expected.
Followed this article previously and setup tailscale, caddy and portainer - https://www.reddit.com/r/Tailscale/comments/104y6nq/docker_tailscale_and_caddy_with_https_a_love_story/
Verified tailscale certificates.

My Caddyfile.

{
	debug
}

(network_paths) {
	handle_path /portainer* {
		reverse_proxy portainer:9443
	}
	handle_path /immich* {
		reverse_proxy immich-server:3001
	}
}

mytailscale.ts.net {
	import network_paths
}

Cleared browser cache, First hit - Caddy Lgos

{"level":"debug","ts":1713160729.8195236,"logger":"events","msg":"event","name":"tls_get_certificate","id":"866c10cb-e5e1-4911-8581-513963bab1e0","origin":"tls","data":{"client_hello":{"CipherSuites":[4865,4867,4866,49195,49199,52393,52392,49196,49200,49162,49161,49171,49172,156,157,47,53],"ServerName":"mytailscale.ts.net","SupportedCurves":[29,23,24,25,256,257],"SupportedPoints":"AA==","SignatureSchemes":[1027,1283,1539,2052,2053,2054,1025,1281,1537,515,513],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[772,771],"RemoteAddr":{"IP":"172.17.0.1","Port":53332,"Zone":""},"LocalAddr":{"IP":"172.17.0.12","Port":443,"Zone":""}}}}
{"level":"debug","ts":1713160729.81964,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"mytailscale.ts.net"}
{"level":"debug","ts":1713160729.8196495,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.mytailscale.ts.net"}
{"level":"debug","ts":1713160729.8196542,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.ts.net"}
{"level":"debug","ts":1713160729.819658,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.*.net"}
{"level":"debug","ts":1713160729.8196619,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.*.*"}
{"level":"debug","ts":1713160729.822786,"logger":"tls.handshake","msg":"using externally-managed certificate","remote_ip":"172.17.0.1","remote_port":"53332","sni":"mytailscale.ts.net","names":["mytailscale.ts.net"],"expiration":1720756580}
{"level":"debug","ts":1713160729.8301804,"logger":"events","msg":"event","name":"tls_get_certificate","id":"6ddb337e-98ac-4358-9367-cd9fa06c840b","origin":"tls","data":{"client_hello":{"CipherSuites":[4865,4867,4866,49195,49199,52393,52392,49196,49200,49162,49161,49171,49172,156,157,47,53],"ServerName":"mytailscale.ts.net","SupportedCurves":[29,23,24,25,256,257],"SupportedPoints":"AA==","SignatureSchemes":[1027,1283,1539,2052,2053,2054,1025,1281,1537,515,513],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[772,771],"RemoteAddr":{"IP":"172.17.0.1","Port":53330,"Zone":""},"LocalAddr":{"IP":"172.17.0.12","Port":443,"Zone":""}}}}
{"level":"debug","ts":1713160729.8303082,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"mytailscale.ts.net"}
{"level":"debug","ts":1713160729.8303173,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.mytailscale.ts.net"}
{"level":"debug","ts":1713160729.8331294,"logger":"tls.handshake","msg":"using externally-managed certificate","remote_ip":"172.17.0.1","remote_port":"53330","sni":"mytailscale.ts.net","names":["mytailscale.ts.net"],"expiration":1720756580}
{"level":"debug","ts":1713160729.8451047,"logger":"http.handlers.rewrite","msg":"rewrote request","request":{"remote_ip":"172.17.0.1","remote_port":"53332","client_ip":"172.17.0.1","proto":"HTTP/2.0","method":"GET","host":"mytailscale.ts.net","uri":"/portainer","headers":{"Te":["trailers"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Gpc":["1"],"Sec-Fetch-User":["?1"],"Dnt":["1"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Site":["none"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"mytailscale.ts.net"}},"method":"GET","uri":"/"}
{"level":"debug","ts":1713160729.8451762,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"portainer:9443","total_upstreams":1}
{"level":"debug","ts":1713160730.000087,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"portainer:9443","duration":0.154801371,"request":{"remote_ip":"172.17.0.1","remote_port":"53332","client_ip":"172.17.0.1","proto":"HTTP/2.0","method":"GET","host":"mytailscale.ts.net","uri":"/","headers":{"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.5"],"Sec-Fetch-Site":["none"],"Dnt":["1"],"X-Forwarded-For":["172.17.0.1"],"Sec-Fetch-User":["?1"],"X-Forwarded-Host":["mytailscale.ts.net"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Gpc":["1"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Mode":["navigate"],"X-Forwarded-Proto":["https"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"mytailscale.ts.net"}},"error":"dial tcp: lookup portainer on 75.75.75.75:53: no such host"}
{"level":"error","ts":1713160730.0001988,"logger":"http.log.error","msg":"dial tcp: lookup portainer on 75.75.75.75:53: no such host","request":{"remote_ip":"172.17.0.1","remote_port":"53332","client_ip":"172.17.0.1","proto":"HTTP/2.0","method":"GET","host":"mytailscale.ts.net","uri":"/portainer","headers":{"Sec-Gpc":["1"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Site":["none"],"Sec-Fetch-User":["?1"],"Dnt":["1"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Mode":["navigate"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"mytailscale.ts.net"}},"duration":0.155116949,"status":502,"err_id":"8jvgdu27s","err_trace":"reverseproxy.statusError (reverseproxy.go:1267)"}
{"level":"debug","ts":1713160730.0528069,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"192.168.0.33:7311","total_upstreams":1}
{"level":"debug","ts":1713160730.0533082,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"192.168.0.33:7311","duration":0.000433821,"request":{"remote_ip":"172.17.0.1","remote_port":"53332","client_ip":"172.17.0.1","proto":"HTTP/2.0","method":"GET","host":"mytailscale.ts.net","uri":"/favicon.ico","headers":{"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0"],"Sec-Fetch-Site":["same-origin"],"Sec-Gpc":["1"],"Sec-Fetch-Dest":["image"],"Accept-Encoding":["gzip, deflate, br"],"X-Forwarded-For":["172.17.0.1"],"Te":["trailers"],"Accept":["image/avif,image/webp,*/*"],"Dnt":["1"],"Sec-Fetch-Mode":["no-cors"],"Accept-Language":["en-US,en;q=0.5"],"Referer":["https://mytailscale.ts.net/portainer"],"X-Forwarded-Proto":["https"],"X-Forwarded-Host":["mytailscale.ts.net"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"mytailscale.ts.net"}},"headers":{"Date":["Mon, 15 Apr 2024 05:58:50 GMT"],"Content-Type":["text/html"],"Content-Length":["248"],"Server":["noindex"]},"status":400}

Subsequent hits Caddy logs

{"level":"debug","ts":1713160421.1754978,"logger":"http.handlers.rewrite","msg":"rewrote request","request":{"remote_ip":"172.17.0.1","remote_port":"53112","client_ip":"172.17.0.1","proto":"HTTP/2.0","method":"GET","host":"mytailscale.ts.net","uri":"/immich","headers":{"Sec-Fetch-Site":["none"],"Sec-Fetch-User":["?1"],"Te":["trailers"],"Accept-Encoding":["gzip, deflate, br"],"Cookie":[],"Sec-Fetch-Mode":["navigate"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Dest":["document"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.5"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"mytailscale.ts.net"}},"method":"GET","uri":"/"}
{"level":"debug","ts":1713160421.175591,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"immich-server:3001","total_upstreams":1}
{"level":"debug","ts":1713160421.750604,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"immich-server:3001","duration":0.574918719,"request":{"remote_ip":"172.17.0.1","remote_port":"53112","client_ip":"172.17.0.1","proto":"HTTP/2.0","method":"GET","host":"mytailscale.ts.net","uri":"/","headers":{"Sec-Fetch-Dest":["document"],"Accept-Encoding":["gzip, deflate, br"],"Te":["trailers"],"X-Forwarded-Proto":["https"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0"],"Upgrade-Insecure-Requests":["1"],"X-Forwarded-Host":["mytailscale.ts.net"],"Cookie":[],"Sec-Fetch-User":["?1"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.5"],"X-Forwarded-For":["172.17.0.1"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Site":["none"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"mytailscale.ts.net"}},"error":"dial tcp: lookup immich-server on 75.75.75.75:53: no such host"}
{"level":"error","ts":1713160421.750748,"logger":"http.log.error","msg":"dial tcp: lookup immich-server on 75.75.75.75:53: no such host","request":{"remote_ip":"172.17.0.1","remote_port":"53112","client_ip":"172.17.0.1","proto":"HTTP/2.0","method":"GET","host":"mytailscale.ts.net","uri":"/immich","headers":{"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.5"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Dest":["document"],"Accept-Encoding":["gzip, deflate, br"],"Cookie":[],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Site":["none"],"Sec-Fetch-User":["?1"],"Te":["trailers"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"mytailscale.ts.net"}},"duration":0.575306664,"status":502,"err_id":"u7euw40er","err_trace":"reverseproxy.statusError (reverseproxy.go:1267)"}

That’s not an error, that’s a debug log. All it’s saying is “I tried looking for a cert that’s an exact match in memory, but didn’t find one”. Then it tries again, using a wildcard * instead, and repeats until all domain segments have been tried. Then finally, it triggers the get_certificate logic to fetch it from Tailscale because it wasn’t in memory.

The actual error is here:

Are you sure portainer is the correct service name? I don’t see it in the docker compose files you posted so I can’t help you verify.

In your Caddy compose config, I think this is wrong. Shouldn’t it be network:, listing bridge as one of the networks?

Thanks for your support so far.
I’ve corrected the network_mode: bridge.
Verified all the containers including Caddy are within the same network.

    networks:
      - bridge

Also corrected the Caddyfile, container name is “portainer-ce”. which is working now.

	handle_path /portainer* {
		reverse_proxy portainer-ce:9000
	}
       	handle_path /immich* {
		reverse_proxy immich-server:3001
	}

I have 2 open questions.

  1. Please help on how to achieve this. - “You can/should remove port mappings from those containers as well, so that they’re only accessible through Caddy, as your proxy”.
  2. Immich, teslamate and grafana still aren’t working.
    There is no error captured in the logs. Here is the full Caddy log for immich.
{"level":"debug","ts":1713302064.779253,"logger":"events","msg":"event","name":"tls_get_certificate","id":"01f7e3c3-f62a-4b2f-9d77-43fd369a14eb","origin":"tls","data":{"client_hello":{"CipherSuites":[4865,4867,4866,49195,49199,52393,52392,49196,49200,49162,49161,49171,49172,156,157,47,53],"ServerName":"mytailscale.ts.net","SupportedCurves":[29,23,24,25,256,257],"SupportedPoints":"AA==","SignatureSchemes":[1027,1283,1539,2052,2053,2054,1025,1281,1537,515,513],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[772,771],"RemoteAddr":{"IP":"172.23.0.1","Port":50114,"Zone":""},"LocalAddr":{"IP":"172.23.0.2","Port":443,"Zone":""}}}}
{"level":"debug","ts":1713302064.7793734,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"mytailscale.ts.net"}
{"level":"debug","ts":1713302064.7793837,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.chipmunk-tailor.ts.net"}
{"level":"debug","ts":1713302064.7793887,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.ts.net"}
{"level":"debug","ts":1713302064.7793925,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.*.net"}
{"level":"debug","ts":1713302064.7827106,"logger":"tls.handshake","msg":"using externally-managed certificate","remote_ip":"172.23.0.1","remote_port":"50114","sni":"mytailscale.ts.net","names":["mytailscale.ts.net"],"expiration":1720934582}
{"level":"debug","ts":1713302064.7912538,"logger":"http.handlers.rewrite","msg":"rewrote request","request":{"remote_ip":"172.23.0.1","remote_port":"50114","client_ip":"172.23.0.1","proto":"HTTP/2.0","method":"GET","host":"mytailscale.ts.net","uri":"/immich","headers":{"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"Dnt":["1"],"Sec-Gpc":["1"],"Sec-Fetch-Mode":["navigate"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Site":["none"],"Sec-Fetch-User":["?1"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"mytailscale.ts.net"}},"method":"GET","uri":"/"}
{"level":"debug","ts":1713302064.7913032,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"immich-server:3001","total_upstreams":1}
{"level":"debug","ts":1713302064.8047934,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"immich-server:3001","duration":0.013427374,"request":{"remote_ip":"172.23.0.1","remote_port":"50114","client_ip":"172.23.0.1","proto":"HTTP/2.0","method":"GET","host":"mytailscale.ts.net","uri":"/","headers":{"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"],"X-Forwarded-Proto":["https"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Dest":["document"],"Te":["trailers"],"Accept-Language":["en-US,en;q=0.5"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0"],"Sec-Fetch-Mode":["navigate"],"Dnt":["1"],"X-Forwarded-For":["172.23.0.1"],"X-Forwarded-Host":["mytailscale.ts.net"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Site":["none"],"Sec-Fetch-User":["?1"],"Sec-Gpc":["1"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"mytailscale.ts.net"}},"headers":{"Etag":["W/\"848-1712284967000\""],"Date":["Tue, 16 Apr 2024 21:14:24 GMT"],"Keep-Alive":["timeout=5"],"X-Powered-By":["Express"],"Vary":["Accept-Encoding"],"Content-Encoding":["br"],"Connection":["keep-alive"],"Content-Length":["848"],"Content-Type":["text/html;charset=utf-8"],"Last-Modified":["Fri, 05 Apr 2024 02:42:47 GMT"]},"status":200}

However, inspecting on the browser, found this error -

“Loading module from “https://mytailscale/REDACTED_PATH.js” was blocked because of a disallowed MIME type (“”)”

Testing directly with 192.168.0.1:2283 loads the immich login page.
I’m not sure how to proceed, tried searching online but haven’t been able to resolve it.

Just remove ports: from your compose files for your other containers (not Caddy obviously).

Please never say “aren’t working”. Be specific about the behaviour you’re seeing. That doesn’t mean anything on its own.

Seems like it’s responding just fine. So the problem isn’t with Caddy, it’s with the app itself.

Read the docs for the apps that aren’t working to see if they have any specific config requirements if being proxied.

I see you’re using handle_path, that can cause problems:

Using subdomains for each app is the safest approach.

Thanks a ton @francislavoie!!! You made my weeks of efforts come to life.
Following the documentation and other examples online, was able to setup my Godaddy subdomains as reverse proxy on Caddy, and now everything works flawlessly!!

One last thing, i did try to remove the ports: as you mentioned from the compose files and redeployed the containers.

Tried updating Caddyfile to below, and it doesn’t seem to route the requests to the containers. Is this correct?

sub1.mygodaddydomain.com {
		reverse_proxy portainer-ce
	}
1 Like

No, your Caddyfile still needs to have the port number. You just don’t need to bind the port to the host in your compose.yml file.

Pardon my lack of Caddy knowledge, but how will caddy know that the container is running on specific port after we remove the port mapping in the compose.yml?

Because you tell Caddy the port in your Caddyfile. You can’t omit the port (unless you know it’s listening on port 80, the default for Caddy’s proxy).

That mapping is binding a port that is being listened to inside the container to a port outside the container. You don’t need this at all.

When containers connect to eachother (via their service name as the “domain”, which resolves to the container’s assigned IP address), they do it through the Docker Network (a virtual network set up by Docker), not through the host machine itself.

The apps you run will document which port they listen on by default (9000 for Portainer, 3001 for Immich) and you just proxy to that port on those containers.

1 Like