TP Link Router reverse proxy failing

1. The problem I’m having:

Hi, so this is my first post, and have read the guidelines, but if I have missed something, please let me know and will happily learn :slight_smile:

So I have only recently moved over to caddy and have successfully ported over my nginx proxy manager config into the caddyFile setup (to be honest it was easier than I thought and the system means I have ALOT less “advanced settings” enabled.. which is great. So far very happy.

I have hit my first bump in the road that I can’t seem to get around.. I am trying to proxy my router, as prefer to go to router.home.example.com rather than 192.168…

However it seems like I am stuck in a weird loop, So what happens:

I go to the proxy site, it loads as normal (checked F12 and also the logs (see below) and everything is returning fine.

When I enter my password (which I have checked against going directly to the router site), it 403’s me (again see below).. from my point of view I get no popup etc, I just get a reloaded page with no password entered.

I am completely stuck on what to do and why it is happening, and looking for some help if possible?

2. Error messages and/or full log output:

192.168.87.92 - - [27/Aug/2025:14:31:53 +0000] "GET / HTTP/3.0" 304 0
192.168.87.92 - - [27/Aug/2025:14:31:53 +0000] "GET /webpages/index.html HTTP/3.0" 304 0
192.168.87.92 - - [27/Aug/2025:14:31:53 +0000] "GET /webpages/config.json HTTP/3.0" 304 0
192.168.87.92 - - [27/Aug/2025:14:31:54 +0000] "POST /cgi-bin/luci/;stok=/locale?form=lang HTTP/3.0" 200 137
192.168.87.92 - - [27/Aug/2025:14:31:54 +0000] "POST /cgi-bin/luci/;stok=/locale?form=country HTTP/3.0" 200 40
192.168.87.92 - - [27/Aug/2025:14:31:54 +0000] "POST /cgi-bin/luci/;stok=/locale?form=list HTTP/3.0" 200 817
192.168.87.92 - - [27/Aug/2025:14:31:54 +0000] "POST /cgi-bin/luci/;stok=/device_config?form=config HTTP/3.0" 200 4496
192.168.87.92 - - [27/Aug/2025:14:31:55 +0000] "POST /cgi-bin/luci/;stok=/login?form=check_factory_default HTTP/3.0" 200 44
192.168.87.92 - - [27/Aug/2025:14:31:55 +0000] "POST /cgi-bin/luci/;stok=/device_config?form=config HTTP/3.0" 200 4496
192.168.87.92 - - [27/Aug/2025:14:31:55 +0000] "POST /cgi-bin/luci/;stok=/login?form=keys HTTP/3.0" 200 336
192.168.87.92 - - [27/Aug/2025:14:31:55 +0000] "POST /cgi-bin/luci/;stok=/login?form=sysmode HTTP/3.0" 200 57
192.168.87.92 - - [27/Aug/2025:14:31:56 +0000] "POST /cgi-bin/luci/;stok=/domain_login?form=dlogin HTTP/3.0" 200 182
192.168.87.92 - - [27/Aug/2025:14:34:11 +0000] "POST /cgi-bin/luci/;stok=/login?form=auth HTTP/3.0" 200 189
192.168.87.92 - - [27/Aug/2025:14:34:11 +0000] "POST /cgi-bin/luci/;stok=/login?form=login HTTP/3.0" 403 0
192.168.87.92 - - [27/Aug/2025:14:34:11 +0000] "GET / HTTP/3.0" 304 0
192.168.87.92 - - [27/Aug/2025:14:34:11 +0000] "GET /webpages/index.html HTTP/3.0" 304 0
192.168.87.92 - - [27/Aug/2025:14:34:11 +0000] "GET /webpages/config.json HTTP/3.0" 304 0

3. Caddy version:

v2.10.2 h1:g/gTYjGMD0dec+UgMw8SnfmJ3I9+M2TdvoRL/Ovu6U8=

4. How I installed and ran Caddy:

a. System environment:

Docker setup, using docker compose

b. Command:

To reload the caddyfile :

docker compose exec -w /etc/caddy caddy caddy reload 

c. Service/unit/compose file:

 caddy:
    image: tailuk/caddy-server:latest
    hostname: caddy
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    # logging:
    #   driver: "journald"
    #   options:
    #     tag: "caddy" # Optional: Add a tag to easily identify logs in journalctl
    env_file: .env
    volumes:
      - ./caddy/conf:/etc/caddy
      - ./caddy/site:/srv
      - ./caddy/data:/data
      - ./caddy/config:/config
      - ./caddy/log:/log
    networks:
      proxy_network:
        ipv4_address: 172.18.250.254 # Set this so that we have a standard route 

d. My complete Caddy config:

# Global options block to enable the DNS challenge
{
	email bob@bob.com # Optional but recommended
	acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
	# Trust proxies for all reverse proxy directives in this Caddyfile
	servers {
		trusted_proxies static 172.18.0.0/16
	}
	log default {
		output file /log/caddy_access.log {
			roll_size 10mb
			roll_keep 10
			roll_keep_for 720h
		}
	}
}

(subdomain-log) {
	log {
		hostnames {args[0]}
		format transform "{common_log}"
		output file /log/access-{args[0]}.log {
			roll_size 10mb
			roll_keep 5
			roll_keep_for 720h
		}
	}
}

# Certificate for *.example.com
*.example.com, [bathlab.casa](http://example.com) {
	@audiobookshelf host audiobookshelf.example.com
	import subdomain-log audiobookshelf.example.com
	handle @audiobookshelf {
		@autoLaunchblock {
			query autoLaunch=*
			not {
				client_ip 192.168.87.0/24 172.18.0.0/16 10.11.13.0/24 100.64.0.0/24
			}
		}
		# If the matcher is a hit, respond with a 403 Forbidden status
		abort @autoLaunchblock

		reverse_proxy audiobookshelf:80
	}
	@authentik host auth.example.com
	import subdomain-log auth.example.com
	handle @authentik {
		reverse_proxy authentik-server:9000
	}

	@jitsi host jitsi.example.com
	import subdomain-log jitsi.example.com
	handle @jitsi {
		# directive execution order is only as stated if enclosed with route.
		route {
			# always forward outpost path to actual outpost
			reverse_proxy /outpost.goauthentik.io/* http://authentik-server:9000 {
				header_up Host {http.reverse_proxy.upstream.host}
			}

			# forward authentication to outpost
			forward_auth http://authentik-server:9000 {
				uri /outpost.goauthentik.io/auth/caddy

				# capitalization of the headers is important, otherwise they will be empty
				copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Entitlements X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version

				# optional, in this config trust all private ranges, should probably be set to the outposts IP
				#trusted_proxies 192.168.87.0/24 172.18.0.0/16 10.11.13.0/24 100.64.0.0/24 100.122.75.96 100.66.255.64 100.92.202.16
			}
			reverse_proxy jitsi-web:80
		}
	}

	handle {
		# Unhandled domains fall through to here,
		# but we don't want to accept their requests
		respond 404
	}
}
# Certificate for *.home.bathlab.casa
*.home.bathlab.casa {
	@allowed_ips client_ip 192.168.87.0/24 172.18.0.0/16 10.11.13.0/24 100.64.0.0/24 100.122.75.96 100.66.255.64 100.92.202.16
	# Matchers for allowed IP addresses/subnets
	# Block all other IPs
	@blocked_ips {
		not {
			client_ip 192.168.87.0/24 172.18.0.0/16 10.11.13.0/24 100.64.0.0/24 100.122.75.96 100.66.255.64 100.92.202.16
		}
	}
	handle @blocked_ips {
		# Unhandled domains fall through to here,
		# but we don't want to accept their requests
		respond 404
	}

	# Matcher for a home-network service
	@adguard host adguard.home.bathlab.casa
	import subdomain-log adguard.home.bathlab.casa
	handle @adguard {
		reverse_proxy 192.168.87.22:24248
	}

	@dashboard host dash.home.example.com
	handle @dashboard {
		reverse_proxy homarr:7575
	}

	@frigate host frigate.home.example.com
	handle @frigate {
		# directive execution order is only as stated if enclosed with route.
		route {
			# always forward outpost path to actual outpost
			reverse_proxy /outpost.goauthentik.io/* http://authentik-server:9000 {
				header_up Host {http.reverse_proxy.upstream.host}
			}

			# forward authentication to outpost
			forward_auth http://authentik-server:9000 {
				uri /outpost.goauthentik.io/auth/caddy

				# capitalization of the headers is important, otherwise they will be empty
				copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Entitlements X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version

				# optional, in this config trust all private ranges, should probably be set to the outposts IP
				#trusted_proxies 192.168.87.0/24 172.18.0.0/16 10.11.13.0/24 100.64.0.0/24 100.122.75.96 100.66.255.64 100.92.202.16
			}
			reverse_proxy frigate:5000
		}
	}

	@portainer_i5 host portainer-i5.home.example.com
	handle @portainer_i5 {
		reverse_proxy portainer:9000
	}
	@radarr host radarr.home.example.com
	handle @radarr {
		reverse_proxy radarr:7878
	}

	@readarr host readarr.home.example.com
	handle @readarr {
		reverse_proxy readarr:8787
	}

	@router host router.home.example.com
	import subdomain-log router.home.example.com
	handle @router {
		reverse_proxy 192.168.87.1:80
	}

	handle {
		# Unhandled domains fall through to here,
		# but we don't want to accept their requests
		respond 404
	}
}

5. Links to relevant resources:

I have no links directly. I hvae looked through the caddy docs online : The Caddyfile — Caddy Documentation
As well as searched reddit + this help forum.. but I can’t find anything that seems to reference the experience I am getting on this one site.

FYI - I know it says don’t redact anything. I have done a replace of the servername to example.com - as don’t feel comfortable sharing that.

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