[SOLVED] Reverse proxy subdomain and location

1. Caddy version (caddy version):

2.2.1

2. How I run Caddy:

a. System environment:

OS: Ubuntu 20.04.1 LTS
Docker: 19.03.8
Docker Compose: 1.23.2

b. Command:

docker-compose build # sometimes
docker-compose up -d

c. Service/unit/compose file:

Caddy

---
version: "3.4"

services:
  caddy:
    build: .
    env_file:
      - .env
    container_name: caddy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - caddy_data:/data
    ports:
      - 80:80
      - 443:443
    restart: unless-stopped

volumes:
  caddy_data: {}

Ubooquity

---
version: "3.4"

services:
  ubooquity:
    image: ghcr.io/linuxserver/ubooquity
    container_name: ubooquity
    environment:
      - PUID=992 # ubooquity
      - PGID=995 # media
      - TZ=America/New_York
      - UMASK_SET=002
      - MAXMEM=2048
    volumes:
      - /data/config/ubooquity:/config
      - /data/media:/data/media
    ports:
      - 2202:2202
      - 2203:2203
    networks:
      - caddy
    labels:
      caddy: comics.chrisrees.dev
      caddy.1_reverse_proxy: "{{upstreams 2202}}"
      caddy.2_reverse_proxy: "/admin {{upstreams 2203}}"
      caddy.tls.dns: "cloudflare {env.CLOUDFLARE_API_KEY}"
    restart: unless-stopped

networks:
  caddy:
    external:
      name: caddy_default

d. My complete Caddyfile or JSON config:

alone-in-a-room.com {
        reverse_proxy 172.18.0.10:9099
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
chrisrees.dev {
        reverse_proxy 172.18.0.11:9100
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
comics.chrisrees.dev {
        reverse_proxy 172.18.0.12:2202
        reverse_proxy /admin 172.18.0.12:2203
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
lidarr.chrisrees.dev {
        reverse_proxy 172.18.0.2:8686
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
mylar.chrisrees.dev {
        reverse_proxy 172.18.0.5:8090
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
plex.chrisrees.dev {
        reverse_proxy 172.18.0.7:32400
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
radarr.chrisrees.dev {
        reverse_proxy 172.18.0.6:7878
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
sabnzbd.chrisrees.dev {
        reverse_proxy 172.18.0.4:8080
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
sonarr.chrisrees.dev {
        reverse_proxy 172.18.0.8:8989
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
sync.chrisrees.dev {
        reverse_proxy /* 172.18.0.9:8384
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
{
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [
            ":443"
          ],
          "routes": [
            {
              "match": [
                {
                  "host": [
                    "sabnzbd.chrisrees.dev"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "172.18.0.4:8080"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            },
            {
              "match": [
                {
                  "host": [
                    "comics.chrisrees.dev"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "172.18.0.12:2203"
                            }
                          ]
                        }
                      ],
                      "match": [
                        {
                          "path": [
                            "/admin"
                          ]
                        }
                      ]
                    },
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "172.18.0.12:2202"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            },
            {
              "match": [
                {
                  "host": [
                    "lidarr.chrisrees.dev"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "172.18.0.2:8686"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            },
            {
              "match": [
                {
                  "host": [
                    "radarr.chrisrees.dev"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "172.18.0.6:7878"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            },
            {
              "match": [
                {
                  "host": [
                    "sonarr.chrisrees.dev"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "172.18.0.8:8989"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            },
            {
              "match": [
                {
                  "host": [
                    "alone-in-a-room.com"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "172.18.0.10:9099"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            },
            {
              "match": [
                {
                  "host": [
                    "mylar.chrisrees.dev"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "172.18.0.5:8090"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            },
            {
              "match": [
                {
                  "host": [
                    "plex.chrisrees.dev"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "172.18.0.7:32400"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            },
            {
              "match": [
                {
                  "host": [
                    "sync.chrisrees.dev"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "172.18.0.9:8384"
                            }
                          ]
                        }
                      ],
                      "match": [
                        {
                          "path": [
                            "/*"
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            },
            {
              "match": [
                {
                  "host": [
                    "chrisrees.dev"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "172.18.0.11:9100"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            }
          ]
        }
      }
    },
    "tls": {
      "automation": {
        "policies": [
          {
            "subjects": [
              "sabnzbd.chrisrees.dev",
              "comics.chrisrees.dev",
              "lidarr.chrisrees.dev",
              "radarr.chrisrees.dev",
              "sonarr.chrisrees.dev",
              "alone-in-a-room.com",
              "mylar.chrisrees.dev",
              "plex.chrisrees.dev",
              "sync.chrisrees.dev",
              "chrisrees.dev"
            ],
            "issuer": {
              "challenges": {
                "dns": {
                  "provider": {
                    "api_token": "<TOKEN>",
                    "name": "cloudflare"
                  }
                }
              },
              "module": "acme"
            }
          }
        ]
      }
    }
  }
}

3. The problem I’m having:

I’m specifically focusing on migrating my Ubooquity app to Caddy at the moment. It requires two ports: 2202 and 2203. The first is for the main app, the second is for the admin interface. I’ve tackled this in Nginx by having two location blocks, one at / and one at /admin, both proxying to ports 2202 and 2203 respectively.

With Caddy, my current config mostly works. However, the Admin interface seems to be broken. It claims it’s not connected to the server and all of the minified JS files are claiming syntax errors. I assume this has to do with my config somehow.

The JS files are all at URLs similar to: https://comics.chrisrees.dev/admin-res/sha256-min.js with only the name of the file being different

4. Error messages and/or full log output:

2020/12/29 01:25:05 [INFO] Sending configuration to localhost
{"level":"info","ts":1609205105.3465497,"logger":"admin.api","msg":"received request","method":"POST","host":"localhost:2019","uri":"/load","remote_addr":"127.0.0.1:45832","headers":{"Accept-Encoding":["gzip"],"Content-Length":["2211"],"Content-Type":["application/json"],"User-Agent":["Go-http-client/1.1"]}}
{"level":"info","ts":1609205105.3475356,"logger":"admin","msg":"admin endpoint started","address":"tcp/localhost:2019","enforce_origin":false,"origins":["localhost:2019","[::1]:2019","127.0.0.1:2019"]}
{"level":"info","ts":1609205105.3479464,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc00068a000"}
{"level":"info","ts":1609205105.3481648,"logger":"http","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":443}
{"level":"info","ts":1609205105.348195,"logger":"http","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
{"level":"info","ts":1609205105.3489304,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["sonarr.chrisrees.dev","alone-in-a-room.com","plex.chrisrees.dev","sabnzbd.chrisrees.dev","radarr.chrisrees.dev","mylar.chrisrees.dev","lidarr.chrisrees.dev","sync.chrisrees.dev","chrisrees.dev"]}
2020/12/29 01:25:09 [INFO] Skipping default Caddyfile because no path is set
[INFO] Skipping configs because swarm is not available
[INFO] Skipping services because swarm is not available
2020/12/29 01:25:09 [INFO] New Caddyfile:
alone-in-a-room.com {
        reverse_proxy 172.18.0.10:9099
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
chrisrees.dev {
        reverse_proxy 172.18.0.11:9100
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
comics.chrisrees.dev {
        reverse_proxy 172.18.0.12:2202
        reverse_proxy /admin 172.18.0.12:2203
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
lidarr.chrisrees.dev {
        reverse_proxy 172.18.0.2:8686
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
mylar.chrisrees.dev {
        reverse_proxy 172.18.0.5:8090
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
plex.chrisrees.dev {
        reverse_proxy 172.18.0.7:32400
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
radarr.chrisrees.dev {
        reverse_proxy 172.18.0.6:7878
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
sabnzbd.chrisrees.dev {
        reverse_proxy 172.18.0.4:8080
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
sonarr.chrisrees.dev {
        reverse_proxy 172.18.0.8:8989
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
sync.chrisrees.dev {
        reverse_proxy /* 172.18.0.9:8384
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
2020/12/29 01:25:09 [INFO] New Config JSON:
{"apps":{"http":{"servers":{"srv0":{"listen":[":443"],"routes":[{"match":[{"host":["sabnzbd.chrisrees.dev"]}],"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"172.18.0.4:8080"}]}]}]}],"terminal":true},{"match":[{"host":["comics.chrisrees.dev"]}],"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"172.18.0.12:2203"}]}],"match":[{"path":["/admin"]}]},{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"172.18.0.12:2202"}]}]}]}],"terminal":true},{"match":[{"host":["lidarr.chrisrees.dev"]}],"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"172.18.0.2:8686"}]}]}]}],"terminal":true},{"match":[{"host":["radarr.chrisrees.dev"]}],"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"172.18.0.6:7878"}]}]}]}],"terminal":true},{"match":[{"host":["sonarr.chrisrees.dev"]}],"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"172.18.0.8:8989"}]}]}]}],"terminal":true},{"match":[{"host":["alone-in-a-room.com"]}],"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"172.18.0.10:9099"}]}]}]}],"terminal":true},{"match":[{"host":["mylar.chrisrees.dev"]}],"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"172.18.0.5:8090"}]}]}]}],"terminal":true},{"match":[{"host":["plex.chrisrees.dev"]}],"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"172.18.0.7:32400"}]}]}]}],"terminal":true},{"match":[{"host":["sync.chrisrees.dev"]}],"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"172.18.0.9:8384"}]}],"match":[{"path":["/*"]}]}]}],"terminal":true},{"match":[{"host":["chrisrees.dev"]}],"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"172.18.0.11:9100"}]}]}]}],"terminal":true}]}}},"tls":{"automation":{"policies":[{"subjects":["sabnzbd.chrisrees.dev","comics.chrisrees.dev","lidarr.chrisrees.dev","radarr.chrisrees.dev","sonarr.chrisrees.dev","alone-in-a-room.com","mylar.chrisrees.dev","plex.chrisrees.dev","sync.chrisrees.dev","chrisrees.dev"],"issuer":{"challenges":{"dns":{"provider":{"api_token":"<TOKEN>","name":"cloudflare"}}},"module":"acme"}}]}}}}
{"level":"error","ts":1609205115.347783,"logger":"admin","msg":"stopping current admin endpoint","error":"shutting down admin server: context deadline exceeded"}
{"level":"info","ts":1609205153.353808,"logger":"tls.cache.maintenance","msg":"stopped background certificate maintenance","cache":"0xc000264230"}
{"level":"info","ts":1609205153.3541718,"msg":"autosaved config","file":"/config/caddy/autosave.json"}
{"level":"info","ts":1609205153.354194,"logger":"admin.api","msg":"load complete"}
2020/12/29 01:25:53 [INFO] Successfully configured localhost
2020/12/29 01:26:10 [INFO] Sending configuration to localhost
{"level":"info","ts":1609205170.237307,"logger":"admin.api","msg":"received request","method":"POST","host":"localhost:2019","uri":"/load","remote_addr":"127.0.0.1:45874","headers":{"Accept-Encoding":["gzip"],"Content-Length":["2537"],"Content-Type":["application/json"],"User-Agent":["Go-http-client/1.1"]}}
{"level":"info","ts":1609205170.238182,"logger":"admin","msg":"admin endpoint started","address":"tcp/localhost:2019","enforce_origin":false,"origins":["localhost:2019","[::1]:2019","127.0.0.1:2019"]}
{"level":"info","ts":1609205170.2384763,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc0001687e0"}
{"level":"info","ts":1609205170.2386327,"logger":"http","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":443}
{"level":"info","ts":1609205170.2386732,"logger":"http","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
{"level":"info","ts":1609205170.2393384,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["plex.chrisrees.dev","sabnzbd.chrisrees.dev","comics.chrisrees.dev","sonarr.chrisrees.dev","alone-in-a-room.com","mylar.chrisrees.dev","sync.chrisrees.dev","chrisrees.dev","lidarr.chrisrees.dev","radarr.chrisrees.dev"]}
{"level":"error","ts":1609205170.2440968,"logger":"http.handlers.reverse_proxy","msg":"aborting with incomplete response","error":"context canceled"}
{"level":"info","ts":1609205173.2439537,"logger":"tls.cache.maintenance","msg":"stopped background certificate maintenance","cache":"0xc00068a000"}
{"level":"info","ts":1609205173.244141,"msg":"autosaved config","file":"/config/caddy/autosave.json"}
{"level":"info","ts":1609205173.244153,"logger":"admin.api","msg":"load complete"}
2020/12/29 01:26:13 [INFO] Successfully configured localhost
{"level":"info","ts":1609205173.7384157,"logger":"admin","msg":"stopped previous server"}

5. What I already tried:

comics.chrisrees.dev {
        reverse_proxy 172.18.0.12:2202
        reverse_proxy /admin 172.18.0.12:2203
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
comics.chrisrees.dev {
        reverse_proxy /* 172.18.0.12:2202
        reverse_proxy /admin 172.18.0.12:2203
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
comics.chrisrees.dev {
        reverse_proxy /* 172.18.0.12:2202
        reverse_proxy /admin* 172.18.0.12:2203
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}
comics.chrisrees.dev {
        reverse_proxy /* 172.18.0.12:2202
        reverse_proxy @admin 172.18.0.12:2203
        @admin {
                path /admin /admin/*
        }
        tls {
                dns cloudflare {env.CLOUDFLARE_API_KEY}
        }
}

I’ve also tried putting the /admin rules first for all of these options except for the @admin attempt. I figured Caddy likely stopped at the first rule it matched and that might be causing issues.

6. Links to relevant resources:

I’m guessing this has some to do with the way Caddy does routing.
Nginx’s location blocks differ somehow from Caddy’s “equivalent” (matchers).

The following article in the Wiki gives some insight regarding that:

From what I understand, this could solve your problem:

comics.chrisrees.dev {
	handle /admin {
		reverse_proxy 172.18.0.12:2203
	}
	handle {
		reverse_proxy 172.18.0.12:2202
	}
	tls {
		dns cloudflare {env.CLOUDFLARE_API_KEY}
	}
}

Or maybe /admin* or /admin/* or any conjugation of that.

Another thing that comes to my mind is a problem with base URLs.
The software you’re using might allow you to configure that.

Following page gives some insight (it’s for Nginx but might help):

2 Likes

Looks like handle with /admin* mostly did the trick. Looks like the files all load, which lets the UI do most of what it needs to do. It’s having trouble with some .map files created by the js? But that’s another problem. I appreciate you taking the time to show me handle as I had seen it but not quite understood it. I’m not sure why it solves my problem but I’ll read through the post you linked and see if it helps

EDIT: looks like the js.map files are only used for debugging and only loaded when the dev console is open in the browser, so we’re good to go!

1 Like

Caddy’s Caddyfile adapter sorts directives by path matcher length, longest first. Not shortest first as @bryanjhv postulated. And you can see the result of the sorting when you run caddy adapt, which outputs the JSON config.

In the JSON, you see handlers in the order they will be executed. Execution ends at the first terminal request handler (reverse_proxy is one such terminal handler, so is respond and file_server), then handlers are run in reverse order back up the chain (so that handlers like encode and header can modify the response).

For your usecase, this should be fine:

reverse_proxy /admin* 172.18.0.12:2203
reverse_proxy 172.18.0.12:2202

The * is important, because path matching is exact in Caddy v2, meaning that /admin would only match exactly /admin but not /admin/foo.

The purpose of handle is to make sets of request handlers be mutually exclusive from eachother. This comes in handy when you want to do drastically different things for certain routes. But in your case, I don’t think it’s necessary.

P.S. @bryanjhv thanks for trying to answer, it’s appreciated :smiley:

3 Likes

For your usecase, this should be fine:

reverse_proxy /admin* 172.18.0.12:2203
reverse_proxy 172.18.0.12:2202

Oh man, I thought I had tried that. I figured ordering might matter (it might match the empty group first if they were reversed) and started tinkering with various formats. I guess I just missed this one. Thanks, and thank you for explaining the ordering and handle as well

1 Like

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