Repository: caddyserver/caddy (language: go)

Issue:
Caddy tries to get certificate for domains only in host matcher within site block
Does JSON deactivate [host matcher](https://caddyserver.com/docs/json/apps/http/servers/routes/match/host/) to activate automatic HTTPS？
Example:
```
{
	order forward_proxy before file_server
	admin off
	log {
		output file /var/log/caddy/access.log
		level ERROR
	}
	storage file_system {
		root /home/tls
	}

	auto_https off
	default_sni *.xx.yy
	cert_issuer zerossl
	acme_dns duckdns b8a33cc1-1111-2222-3333-b049bf07988f {
		override_domain zz.duckdns.org
	}

	servers unix//dev/shm/h1h2c.sock {
		listener_wrappers {
			proxy_protocol
		}
		protocols h1 h2c
	}

	servers 127.0.0.1:8443 {
		listener_wrappers {
			proxy_protocol
			tls
		}
	}
}

:80 {
	redir https://{host}{uri} permanent
}

:88 {
	bind unix//dev/shm/h1h2c.sock

	file_server {
		root /var/www/html
	}
}

:8443, *.xx.yy:8443 {
	bind 127.0.0.1

	forward_proxy {
		basic_auth user pass
		hide_ip
		hide_via
		probe_resistance
	}

	@host2 {
		host wh.xx.yy ch.xx.yy
	}
	file_server @host2 {
		root /var/www/html
	}
}
```
The above example has been normal before. 
After [this master update](https://github.com/caddyserver/caddy/commit/2808de1e30b873f9c8f4693eae59bd56f20ebbbe), the automatic parameter is lost in the TLS configuration, that is, the certificate cannot be applied for *.xx.yy. 
If the above configuration deletes `auto_https off`, it is found that the background program automatically applies for a certificate for wh.xx.yy、ch.xx.yy.
Based on the above example, thinking about the problem of the update: I think it is the most reasonable method to cancel the host matcher to activate automatic HTTPS. Benefit: Makes the Caddyfile well-configured. Make the host matcher flexible.
Caddy tries to get certificate for domains only in host matcher within site block
Does JSON deactivate [host matcher](https://caddyserver.com/docs/json/apps/http/servers/routes/match/host/) to activate automatic HTTPS？
Example:
```
{
	order forward_proxy before file_server
	admin off
	log {
		output file /var/log/caddy/access.log
		level ERROR
	}
	storage file_system {
		root /home/tls
	}

	auto_https off
	default_sni *.xx.yy
	cert_issuer zerossl
	acme_dns duckdns b8a33cc1-1111-2222-3333-b049bf07988f {
		override_domain zz.duckdns.org
	}

	servers unix//dev/shm/h1h2c.sock {
		listener_wrappers {
			proxy_protocol
		}
		protocols h1 h2c
	}

	servers 127.0.0.1:8443 {
		listener_wrappers {
			proxy_protocol
			tls
		}
	}
}

:80 {
	redir https://{host}{uri} permanent
}

:88 {
	bind unix//dev/shm/h1h2c.sock

	file_server {
		root /var/www/html
	}
}

:8443, *.xx.yy:8443 {
	bind 127.0.0.1

	forward_proxy {
		basic_auth user pass
		hide_ip
		hide_via
		probe_resistance
	}

	@host2 {
		host wh.xx.yy ch.xx.yy
	}
	file_server @host2 {
		root /var/www/html
	}
}
```
The above example has been normal before. 
After [this master update](https://github.com/caddyserver/caddy/commit/2808de1e30b873f9c8f4693eae59bd56f20ebbbe), the automatic parameter is lost in the TLS configuration, that is, the certificate cannot be applied for *.xx.yy. 
If the above configuration deletes `auto_https off`, it is found that the background program automatically applies for a certificate for wh.xx.yy、ch.xx.yy.
Based on the above example, thinking about the problem of the update: I think it is the most reasonable method to cancel the host matcher to activate automatic HTTPS. Benefit: Makes the Caddyfile well-configured. Make the host matcher flexible.


Discussion / hints:
If you set `auto_https off`, then you're telling Caddy "don't manage certificates for me". So don't set that setting if you _do_ want managed certificates.

I'm not sure I understand the intent of your config. It doesn't make sense to me what you're trying to do.

I strongly recommend removing that `tls` config by the way; let Caddy use its defaults for security parameters, so that when you upgrade, you get the best modern security.
My TLS requirements are: apply for a certificate for *.xx.yy, but not for wh.xx.yy and ch.xx.yy. 
1、v2.6.1 and before (normal)：
```
/caddy adapt --config /usr/local/etc/caddy/Caddyfile --adapter caddyfile --pretty
.......
          {
            "match": [{
              "host": ["wh.xx.yy","ch.xx.yy"]
            }],
            "handle": [{
              "handler": "file_server",
              "root": "/var/www/html"
            }]
          }],
          "tls_connection_policies": [{
            "default_sni": "*.xx.yy"
          }],
          "automatic_https": {
            "disable": true
          }
        }
      }
    },
    "tls": {
      "certificates": {
        "automate": ["*.xx.yy"]
      },
      "automation": {
        "policies": [{
          "issuers": [{
            "module": "zerossl",
            "challenges": {
              "dns": {
                "provider": {
                  "name": "duckdns",
                  "api_token": "b8a33cc1-1111-2222-3333-b049bf07988f",
                  "override_domain": "zz.duckdns.org"
                }
              }
            }
          }]
        }]
      }
    }
  }
}
```
2、After [this master is updated](https://github.com/caddyserver/caddy/commit/2808de1e30b873f9c8f4693eae59bd56f20ebbbe) (the `automatic` parameter is lost in the TLS configuration)：
```
/caddy adapt --config /usr/local/etc/caddy/Caddyfile --adapter caddyfile --pretty
.......
          {
            "match": [{
              "host": ["wh.xx.yy","ch.xx.yy"]
            }],
            "handle": [{
              "handler": "file_server",
              "root": "/var/www/html"
            }]
          }],
          "tls_connection_policies": [{
            "default_sni": "*.xx.yy"
          }],
          "automatic_https": {
            "disable": true
          }
        }
      }
    },
    "tls": {
      "automation": {
        "policies": [{
          "issuers": [{
            "module": "zerossl",
            "challenges": {
              "dns": {
                "provider": {
                  "name": "duckdns",
                  "api_token": "b8a33cc1-1111-2222-3333-b049bf07988f",
                  "override_domain": "zz.duckdns.org"
                }
              }
            }
          }]
        }]
      }
    }
  }
}
```
3、After [this master is updated](https://github.com/caddyserver/caddy/commit/2808de1e30b873f9c8f4693eae59bd56f20ebbbe) , delete `auto_https off` (it is found that the background program automatically applies for a certificate for wh.xx.yy、ch.xx.yy.)：
```
/caddy adapt --config /usr/local/etc/caddy/Caddyfile --adapter caddyfile --pretty
.......
          {
            "match": [{
              "host": ["wh.xx.yy","ch.xx.yy"]
            }],
            "handle": [{
              "handler": "file_server",
              "root": "/var/www/html"
            }]
          }],
          "tls_connection_policies": [{
            "default_sni": "*.xx.yy"
          }]
        }
      }
    },
    "tls": {
      "certificates": {
        "automate": ["*.xx.yy"]
      },
      "automation": {
        "policies": [{
          "issuers": [{
            "module": "zerossl",
            "challenges": {
              "dns": {
                "provider": {
                  "name": "duckdns",
                  "api_token": "b8a33cc1-1111-2222-3333-b049bf07988f",
                  "override_domain": "zz.duckdns.org"
                }
              }
            }
          }]
        }]
      }
    }
  }
}
```
The above comparison shows that 2 and 3 cannot meet my TLS requirements.
> My TLS requirements are: apply for a certificate for *.xx.yy, but not for wh.xx.yy and ch.xx.yy.

I don't understand what you're expecting to happen. Caddy _needs_ a TLS certificate for HTTPS. How could it have solve TLS handshakes for wildcard but _not_ those two subdomains? There's a logic error here.

Why do you need to turn off auto_https? What are you trying to prevent with this? All that does in your case is turn off the HTTP->HTTPS redirects, I think, in your old config. If that's what you want, then use `auto_https disable_redirects`. It's wrong to turn it off completely for that usecase.
> > My TLS requirements are: apply for a certificate for *.xx.yy, but not for wh.xx.yy and ch.xx.yy.
> 
> I don't understand what you're expecting to happen. Caddy _needs_ a TLS certificate for HTTPS. How could it have solve TLS handshakes for wildcard but _not_ those two subdomains? There's a logic error here.
> 
> Why do you need to turn off auto_https? What are you trying to prevent with this? All that does in your case is turn off the HTTP->HTTPS redirects, I think, in your old config. If that's what you want, then use `auto_https disable_redirects`. It's wrong to turn it off completely for that usecase.

I expect to request a certificate only for wildcard domains, not for subdomains. I use SNI for domain name traffic.
I use `auto_https off` to disable certificate requests for subdomains.
I have tested to use `auto_https disable_redirects`, and found that the background is still applying for a certificate for the subdomain.
So how to solve this problem? Applying for a certificate for a subdomain is prohibited.
In addition, I update my description above, I hope the expression is accurate. The above is my actual test, I hope you will understand after trying it.
Ok, well, saying `auto_https off` disables all automatic HTTPS including certificate automation, so it won't make a wildcard certificate for you.

I think the correct thing to do would be to remove `auto_https off`.

@francislavoie I did just notice, however, that his host matcher for `wh.itdiy.gq ch.itdiy.gq` is in a site block, yet the adapted JSON has it at a top-level route, not a subroute:

```
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [
            "127.0.0.1:8443"
          ],
          "routes": [
            {
              "match": [
                {
                  "host": [
                    "wh.xx.yy",
                    "ch.xx.yy"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "headers",
...
```

which seems like a bug. `host` headers used within a site block should be within a subroute so auto-HTTPS isn't applied to it.

I can't look into this for a little while because of my current backlog and some recent family things, but anyone else is welcome to take a look in the meantime!
@mholt that's because the same site block is `:8443, *.xx.yy:8443`. It's ambiguous. How is it supposed to both match all domains, but also only match `*.xx.yy`? The adapter removes the top-level host matcher because it's redundant.

If we didn't remove the host matcher, then we'd have to have two copies of all the site's handlers in the site, which doesn't make sense. What we're doing now is the only sensible option.
Wait, the original post has been edited about a dozen times now. I'm trying to backtrack and see what I'm actually testing locally since it no longer matches what OP has above.

@lxhao61 If you could please try to avoid frequent edits it would make our troubleshooting more effective.
Ok, so yeah, because the site block has a host-less key, it doesn't make sense to have a host matcher, thus we don't put it in a subroute. Currently.

I wonder if we should be putting the route in a subroute, though, to avoid auto-HTTPS on those domains in the host matcher within a site definition. I think we only intend to do auto-HTTPS on domains at the top of a site block.
> Wait, the original post has been edited about a dozen times now. I'm trying to backtrack and see what I'm actually testing locally since it no longer matches what OP has above.
> 
> @lxhao61 If you could please try to avoid frequent edits it would make our troubleshooting more effective.

ok, sorry. I want to express it very accurately, so I edited it, and now I will not edit it anymore.
> I wonder if we should be putting the route in a subroute, though

Yeah that seems sensible.
For reference, the relevant code is here:

https://github.com/caddyserver/caddy/blob/b4e28af953aa02fb44f88c155956488b1f473348/caddyconfig/httpcaddyfile/httptype.go#L1131-L1140

For the sake of cleaner JSON output, I wonder if it'd be worth keeping this logic for the most part, but wrap the site block in a subroute _if a host matcher exists in the site block_. Otherwise, I don't think there's a need to wrap in a subroute and we'd have unnecessarily-overly-nested JSON. :man_shrugging: 
Even more relevant:

https://github.com/caddyserver/caddy/blob/b4e28af953aa02fb44f88c155956488b1f473348/caddyconfig/httpcaddyfile/httptype.go#L910-L914
If you set `auto_https off`, then you're telling Caddy "don't manage certificates for me". So don't set that setting if you _do_ want managed certificates.

I'm not sure I understand the intent of your config. It doesn't make sense to me what you're trying to do.

I strongly recommend removing that `tls` config by the way; let Caddy use its defaults for security parameters, so that when you upgrade, you get the best modern security.
My TLS requirements are: apply for a certificate for *.xx.yy, but not for wh.xx.yy and ch.xx.yy. 
1、v2.6.1 and before (normal)：
```
/caddy adapt --config /usr/local/etc/caddy/Caddyfile --adapter caddyfile --pretty
.......
          {
            "match": [{
              "host": ["wh.xx.yy","ch.xx.yy"]
            }],
            "handle": [{
              "handler": "file_server",
              "root": "/var/www/html"
            }]
          }],
          "tls_connection_policies": [{
            "default_sni": "*.xx.yy"
          }],
          "automatic_https": {
            "disable": true
          }
        }
      }
    },
    "tls": {
      "certificates": {
        "automate": ["*.xx.yy"]
      },
      "automation": {
        "policies": [{
          "issuers": [{
            "module": "zerossl",
            "challenges": {
              "dns": {
                "provider": {
                  "name": "duckdns",
                  "api_token": "b8a33cc1-1111-2222-3333-b049bf07988f",
                  "override_domain": "zz.duckdns.org"
                }
              }
            }
          }]
        }]
      }
    }
  }
}
```
2、After [this master is updated](https://github.com/caddyserver/caddy/commit/2808de1e30b873f9c8f4693eae59bd56f20ebbbe) (the `automatic` parameter is lost in the TLS configuration)：
```
/caddy adapt --config /usr/local/etc/caddy/Caddyfile --adapter caddyfile --pretty
.......
          {
            "match": [{
              "host": ["wh.xx.yy","ch.xx.yy"]
            }],
            "handle": [{
              "handler": "file_server",
              "root": "/var/www/html"
            }]
          }],
          "tls_connection_policies": [{
            "default_sni": "*.xx.yy"
          }],
          "automatic_https": {
            "disable": true
          }
        }
      }
    },
    "tls": {
      "automation": {
        "policies": [{
          "issuers": [{
            "module": "zerossl",
            "challenges": {
              "dns": {
                "provider": {
                  "name": "duckdns",
                  "api_token": "b8a33cc1-1111-2222-3333-b049bf07988f",
                  "override_domain": "zz.duckdns.org"
                }
              }
            }
          }]
        }]
      }
    }
  }
}
```
3、After [this master is updated](https://github.com/caddyserver/caddy/commit/2808de1e30b873f9c8f4693eae59bd56f20ebbbe) , delete `auto_https off` (it is found that the background program automatically applies for a certificate for wh.xx.yy、ch.xx.yy.)：
```
/caddy adapt --config /usr/local/etc/caddy/Caddyfile --adapter caddyfile --pretty
.......
          {
            "match": [{
              "host": ["wh.xx.yy","ch.xx.yy"]
            }],
            "handle": [{
              "handler": "file_server",
              "root": "/var/www/html"
            }]
          }],
          "tls_connection_policies": [{
            "default_sni": "*.xx.yy"
          }]
        }
      }
    },
    "tls": {
      "certificates": {
        "automate": ["*.xx.yy"]
      },
      "automation": {
        "policies": [{
          "issuers": [{
            "module": "zerossl",
            "challenges": {
              "dns": {
                "provider": {
                  "name": "duckdns",
                  "api_token": "b8a33cc1-1111-2222-3333-b049bf07988f",
                  "override_domain": "zz.duckdns.org"
                }
              }
            }
          }]
        }]
      }
    }
  }
}
```
The above comparison shows that 2 and 3 cannot meet my TLS requirements.
> My TLS requirements are: apply for a certificate for *.xx.yy, but not for wh.xx.yy and ch.xx.yy.

I don't understand what you're expecting to happen. Caddy _needs_ a TLS certificate for HTTPS. How could it have solve TLS handshakes for wildcard but _not_ those two subdomains? There's a logic error here.

Why do you need to turn off auto_https? What are you trying to prevent with this? All that does in your case is turn off the HTTP->HTTPS redirects, I think, in your old config. If that's what you want, then use `auto_https disable_redirects`. It's wrong to turn it off completely for that usecase.
> > My TLS requirements are: apply for a certificate for *.xx.yy, but not for wh.xx.yy and ch.xx.yy.
> 
> I don't understand what you're expecting to happen. Caddy _needs_ a TLS certificate for HTTPS. How could it have solve TLS handshakes for wildcard but _not_ those two subdomains? There's a logic error here.
> 
> Why do you need to turn off auto_https? What are you trying to prevent with this? All that does in your case is turn off the HTTP->HTTPS redirects, I think, in your old config. If that's what you want, then use `auto_https disable_redirects`. It's wrong to turn it off completely for that usecase.

I expect to request a certificate only for wildcard domains, not for subdomains. I use SNI for domain name traffic.
I use `auto_https off` to disable certificate requests for subdomains.
I have tested to use `auto_https disable_redirects`, and found that the background is still applying for a certificate for the subdomain.
So how to solve this problem? Applying for a certificate for a subdomain is prohibited.
In addition, I update my description above, I hope the expression is accurate. The above is my actual test, I hope you will understand after trying it.
Ok, well, saying `auto_https off` disables all automatic HTTPS including certificate automation, so it won't make a wildcard certificate for you.

I think the correct thing to do would be to remove `auto_https off`.

@francislavoie I did just notice, however, that his host matcher for `wh.itdiy.gq ch.itdiy.gq` is in a site block, yet the adapted JSON has it at a top-level route, not a subroute:

```
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [
            "127.0.0.1:8443"
          ],
          "routes": [
            {
              "match": [
                {
                  "host": [
                    "wh.xx.yy",
                    "ch.xx.yy"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "headers",
...
```

which seems like a bug. `host` headers used within a site block should be within a subroute so auto-HTTPS isn't applied to it.

I can't look into this for a little while because of my current backlog and some recent family things, but anyone else is welcome to take a look in the meantime!
@mholt that's because the same site block is `:8443, *.xx.yy:8443`. It's ambiguous. How is it supposed to both match all domains, but also only match `*.xx.yy`? The adapter removes the top-level host matcher because it's redundant.

If we didn't remove the host matcher, then we'd have to have two copies of all the site's handlers in the site, which doesn't make sense. What we're doing now is the only sensible option.
Wait, the original post has been edited about a dozen times now. I'm trying to backtrack and see what I'm actually testing locally since it no longer matches what OP has above.

@lxhao61 If you could please try to avoid frequent edits it would make our troubleshooting more effective.
Ok, so yeah, because the site block has a host-less key, it doesn't make sense to have a host matcher, thus we don't put it in a subroute. Currently.

I wonder if we should be putting the route in a subroute, though, to avoid auto-HTTPS on those domains in the host matcher within a site definition. I think we only intend to do auto-HTTPS on domains at the top of a site block.
> Wait, the original post has been edited about a dozen times now. I'm trying to backtrack and see what I'm actually testing locally since it no longer matches what OP has above.
> 
> @lxhao61 If you could please try to avoid frequent edits it would make our troubleshooting more effective.

ok, sorry. I want to express it very accurately, so I edited it, and now I will not edit it anymore.
> I wonder if we should be putting the route in a subroute, though

Yeah that seems sensible.
For reference, the relevant code is here:

https://github.com/caddyserver/caddy/blob/b4e28af953aa02fb44f88c155956488b1f473348/caddyconfig/httpcaddyfile/httptype.go#L1131-L1140

For the sake of cleaner JSON output, I wonder if it'd be worth keeping this logic for the most part, but wrap the site block in a subroute _if a host matcher exists in the site block_. Otherwise, I don't think there's a need to wrap in a subroute and we'd have unnecessarily-overly-nested JSON. :man_shrugging: 
Even more relevant:

https://github.com/caddyserver/caddy/blob/b4e28af953aa02fb44f88c155956488b1f473348/caddyconfig/httpcaddyfile/httptype.go#L910-L914

Fix patch:
diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go
index c220c066d87..77f990b9a5e 100644
--- a/caddyconfig/httpcaddyfile/httptype.go
+++ b/caddyconfig/httpcaddyfile/httptype.go
@@ -907,11 +907,32 @@ func appendSubrouteToRouteList(routeList caddyhttp.RouteList,
 		return routeList
 	}
 
+	// No need to wrap the handlers in a subroute if this is the only server block
+	// and there is no matcher for it (doing so would produce unnecessarily nested
+	// JSON), *unless* there is a host matcher within this site block; if so, then
+	// we still need to wrap in a subroute because otherwise the host matcher from
+	// the inside of the site block would be a top-level host matcher, which is
+	// subject to auto-HTTPS (cert management), and using a host matcher within
+	// a site block is a valid, common pattern for excluding domains from cert
+	// management, leading to unexpected behavior; see issue #5124.
+	wrapInSubroute := true
 	if len(matcherSetsEnc) == 0 && len(p.serverBlocks) == 1 {
-		// no need to wrap the handlers in a subroute if this is
-		// the only server block and there is no matcher for it
-		routeList = append(routeList, subroute.Routes...)
-	} else {
+		var hasHostMatcher bool
+	outer:
+		for _, route := range subroute.Routes {
+			for _, ms := range route.MatcherSetsRaw {
+				for matcherName := range ms {
+					if matcherName == "host" {
+						hasHostMatcher = true
+						break outer
+					}
+				}
+			}
+		}
+		wrapInSubroute = hasHostMatcher
+	}
+
+	if wrapInSubroute {
 		route := caddyhttp.Route{
 			// the semantics of a site block in the Caddyfile dictate
 			// that only the first matching one is evaluated, since
@@ -929,7 +950,10 @@ func appendSubrouteToRouteList(routeList caddyhttp.RouteList,
 		if len(route.MatcherSetsRaw) > 0 || len(route.HandlersRaw) > 0 {
 			routeList = append(routeList, route)
 		}
+	} else {
+		routeList = append(routeList, subroute.Routes...)
 	}
+
 	return routeList
 }
 
diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go
index 33d96d86752..0943b32df59 100644
--- a/modules/caddyhttp/app.go
+++ b/modules/caddyhttp/app.go
@@ -66,7 +66,7 @@ func init() {
 // `{http.request.orig_uri}` | The request's original URI
 // `{http.request.port}` | The port part of the request's Host header
 // `{http.request.proto}` | The protocol of the request
-// `{http.request.remote.host}` | The host part of the remote client's address
+// `{http.request.remote.host}` | The host (IP) part of the remote client's address
 // `{http.request.remote.port}` | The port part of the remote client's address
 // `{http.request.remote}` | The address of the remote client
 // `{http.request.scheme}` | The request scheme


Test patch:
diff --git a/caddytest/integration/caddyfile_adapt/global_server_options_multi.txt b/caddytest/integration/caddyfile_adapt/global_server_options_multi.txt
index c01173b4f8d..ca5306fd7ab 100644
--- a/caddytest/integration/caddyfile_adapt/global_server_options_multi.txt
+++ b/caddytest/integration/caddyfile_adapt/global_server_options_multi.txt
@@ -3,9 +3,7 @@
 		timeouts {
 			idle 90s
 		}
-		protocol {
-			strict_sni_host insecure_off
-		}
+		strict_sni_host insecure_off
 	}
 	servers :80 {
 		timeouts {
@@ -16,9 +14,7 @@
 		timeouts {
 			idle 30s
 		}
-		protocol {
-			strict_sni_host
-		}
+		strict_sni_host
 	}
 }
 
diff --git a/caddytest/integration/caddyfile_adapt/php_fastcgi_matcher.txt b/caddytest/integration/caddyfile_adapt/php_fastcgi_matcher.txt
index d90b2cb4e2b..e5b331e31df 100644
--- a/caddytest/integration/caddyfile_adapt/php_fastcgi_matcher.txt
+++ b/caddytest/integration/caddyfile_adapt/php_fastcgi_matcher.txt
@@ -1,7 +1,12 @@
 :8884
 
-@api host example.com
-php_fastcgi @api localhost:9000
+# the use of a host matcher here should cause this
+# site block to be wrapped in a subroute, even though
+# the site block does not have a hostname; this is
+# to prevent auto-HTTPS from picking up on this host
+# matcher because it is not a key on the site block
+@test host example.com
+php_fastcgi @test localhost:9000
 ----------
 {
 	"apps": {
@@ -13,13 +18,6 @@ php_fastcgi @api localhost:9000
 					],
 					"routes": [
 						{
-							"match": [
-								{
-									"host": [
-										"example.com"
-									]
-								}
-							],
 							"handle": [
 								{
 									"handler": "subroute",
@@ -27,82 +25,99 @@ php_fastcgi @api localhost:9000
 										{
 											"handle": [
 												{
-													"handler": "static_response",
-													"headers": {
-														"Location": [
-															"{http.request.orig_uri.path}/"
-														]
-													},
-													"status_code": 308
-												}
-											],
-											"match": [
-												{
-													"file": {
-														"try_files": [
-															"{http.request.uri.path}/index.php"
-														]
-													},
-													"not": [
+													"handler": "subroute",
+													"routes": [
 														{
-															"path": [
-																"*/"
+															"handle": [
+																{
+																	"handler": "static_response",
+																	"headers": {
+																		"Location": [
+																			"{http.request.orig_uri.path}/"
+																		]
+																	},
+																	"status_code": 308
+																}
+															],
+															"match": [
+																{
+																	"file": {
+																		"try_files": [
+																			"{http.request.uri.path}/index.php"
+																		]
+																	},
+																	"not": [
+																		{
+																			"path": [
+																				"*/"
+																			]
+																		}
+																	]
+																}
 															]
-														}
-													]
-												}
-											]
-										},
-										{
-											"handle": [
-												{
-													"handler": "rewrite",
-													"uri": "{http.matchers.file.relative}"
-												}
-											],
-											"match": [
-												{
-													"file": {
-														"split_path": [
-															".php"
-														],
-														"try_files": [
-															"{http.request.uri.path}",
-															"{http.request.uri.path}/index.php",
-															"index.php"
-														]
-													}
-												}
-											]
-										},
-										{
-											"handle": [
-												{
-													"handler": "reverse_proxy",
-													"transport": {
-														"protocol": "fastcgi",
-														"split_path": [
-															".php"
-														]
-													},
-													"upstreams": [
+														},
 														{
-															"dial": "localhost:9000"
+															"handle": [
+																{
+																	"handler": "rewrite",
+																	"uri": "{http.matchers.file.relative}"
+																}
+															],
+															"match": [
+																{
+																	"file": {
+																		"split_path": [
+																			".php"
+																		],
+																		"try_files": [
+																			"{http.request.uri.path}",
+																			"{http.request.uri.path}/index.php",
+																			"index.php"
+																		]
+																	}
+																}
+															]
+														},
+														{
+															"handle": [
+																{
+																	"handler": "reverse_proxy",
+																	"transport": {
+																		"protocol": "fastcgi",
+																		"split_path": [
+																			".php"
+																		]
+																	},
+																	"upstreams": [
+																		{
+																			"dial": "localhost:9000"
+																		}
+																	]
+																}
+															],
+															"match": [
+																{
+																	"path": [
+																		"*.php"
+																	]
+																}
+															]
 														}
 													]
 												}
 											],
 											"match": [
 												{
-													"path": [
-														"*.php"
+													"host": [
+														"example.com"
 													]
 												}
 											]
 										}
 									]
 								}
-							]
+							],
+							"terminal": true
 						}
 					]
 				}