Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Caddy responds 200 for unmatched routes #4026

Closed
jeyemwey opened this issue Feb 17, 2021 · 6 comments
Closed

Caddy responds 200 for unmatched routes #4026

jeyemwey opened this issue Feb 17, 2021 · 6 comments
Labels
invalid ❓ This doesn't seem right

Comments

@jeyemwey
Copy link

Hey, I'm not sure if this is a bug per se, but it's certainly surprising behavior: I have a caddy v2 server with this caddyfile:

subdomain.example.com {
  reverse_proxy /api/* http://127.0.0.1:8080
}

As expected, any request to /api/* is forwarded to my backend server on :8080. However, a request to /something_else/ is responded with a 200 OK and Content-Length: 0.

This response is very surprising to me, I would have either expected a 404 Not Found or a 503 Service Unavailable response, indicating that the route was not matched. 200 OK is almost certainly not the right move here.

@francislavoie
Copy link
Member

francislavoie commented Feb 17, 2021

This is intended behaviour. Caddy is behaving as configured. You didn't tell it how to handle requests that aren't /api/*. It's not an error on Caddy's part that it doesn't know how to handle the request, so returning a 4xx or 5xx status doesn't really make sense. It simply did what it was told to do.

Some prior discussion: #3226, #3445 (comment)

I recommend writing your config like this:

subdomain.example.com {
	handle /api/* {
		reverse_proxy http://127.0.0.1:8080
	}

	handle {
		# Fallback for any otherwise unhandled requests, maybe a 404 if you want
		respond "Nope!" 404
	}
}

@francislavoie francislavoie added the invalid ❓ This doesn't seem right label Feb 17, 2021
@jeyemwey
Copy link
Author

Thanks for your help, I understand it better now. :)

@francislavoie
Copy link
Member

Glad I was able to clear that up 😄

For next time, please ask your usage questions on the Caddy community forums. We prefer to keep the GitHub issue board for bugs and feature requests. Don't forget to fill out the thread template so we can help you!

@djeikyb
Copy link

djeikyb commented Jun 14, 2022

Just wasted a ton of time because of this behaviour. Could the reverse proxy docs recommend a "finally" block? I'm using this on my local machine to map names to docker services. The test tld is reserved for this purpose which is great. But you have to manually specify port 80 to disable ssl, which is fine now that I know, but was a major and long source of pain. And then today I found out the default "no match" behaviour is.. a 200 response? So I made a typo in the hostname of a long url and didn't realize the 200 response was caddy's "no match". I thought it was coming from the service. With an explicit finally block, next time I make a typo I'll hopefully go straight to looking at the hostname instead of debugging the service.

rabbit.test:80 rmq.test:80 {
	reverse_proxy 127.0.0.1:15672
}

amqp.test:80 {
	reverse_proxy 127.0.0.1:5672
}

# Finally, help yourself debug typos eg rabit.test
# Must be last! <-- EDIT: this is wrong, see replies below
:80 {
	respond "Request matched no routes in the caddy config." 404
}

@mholt
Copy link
Member

mholt commented Jun 15, 2022

@djeikyb Sorry to hear about the time spent on troubleshooting.

But you have to manually specify port 80 to disable ssl, which is fine now that I know, but was a major and long source of pain.

We've done our best to make that clear in the docs.

Automatic HTTPS can be disabled numerous ways: https://caddyserver.com/docs/automatic-https#activation

Any of the following will prevent automatic HTTPS from being activated, either in whole or in part:

  • Explicitly disabling it
  • Not providing any hostnames or IP addresses in the config
  • Listening exclusively on the HTTP port
  • Manually loading certificates (unless this config property is true)

With the Caddyfile specifically, you can also do it by specifying the http:// scheme: https://caddyserver.com/docs/caddyfile/concepts#addresses

Automatic HTTPS is enabled if your site's address contains a hostname or IP address. This behavior is purely implicit, however, so it never overrides any explicit configuration. For example, if the site's address is http://example.com, auto-HTTPS will not activate because the scheme is explicitly http://.

And hopefully at least some of that is intuitive, too. For example if you listen only on the HTTP port, HTTPS won't be enabled; or if you specify the http:// scheme in your config, it will not use HTTPS.

And then today I found out the default "no match" behaviour is.. a 200 response?

There is no standing assumption that an HTTP route config has to "match" anything in the first place; matchers are simply filters. They aren't required nor are they mutually exclusive. So yeah, the default response is an empty response that tells you that the server did what it was configured to do and that it is working. 🤷‍♂️

# Must be last!

The catch-all site does not need to be last:

$ cat Caddyfile
:1234 {
	respond ":1234!"
}

http://localhost:1234 {
	respond "localhost!"
}

$ caddy start

$ curl -v "http://localhost:1234"
*   Trying 127.0.0.1:1234...
* Connected to localhost (127.0.0.1) port 1234 (#0)
> GET / HTTP/1.1
> Host: localhost:1234
> User-Agent: curl/7.81.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: Caddy
< Date: Wed, 15 Jun 2022 01:30:37 GMT
< Content-Length: 10
< 
* Connection #0 to host localhost left intact
localhost!

$ curl -v "http://127.0.0.1:1234"
*   Trying 127.0.0.1:1234...
* Connected to 127.0.0.1 (127.0.0.1) port 1234 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:1234
> User-Agent: curl/7.81.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: Caddy
< Date: Wed, 15 Jun 2022 01:30:42 GMT
< Content-Length: 6
< 
* Connection #0 to host 127.0.0.1 left intact
:1234!

@djeikyb
Copy link

djeikyb commented Jun 15, 2022

re: unmatched route.. Ah, I assumed order would matter, thanks for the correction!

re: ssl.. I'd read the automated_https you linked, and followed the link "Explicitly disabling it", but I wasn't sure how to use that information. I started with the reverse proxy quick start, which is astonishingly simple. Growing my understanding and config from those one or two lines into a few brace scoped blocks that map names to reverse_proxy instructions was also wonderfully easy. It was disabling ssl that was a steep climb. This line makes sense to me now "Listening exclusively on the HTTP port", but I think I would have gained understanding faster with an example that looks like:

# Can browse to https://rabbit.test, ssl is enabled by default
rabbit.test {
	reverse_proxy 127.0.0.1:15672
}

# Disable ssl by specifying the http port
rabbit.test:80 {
	reverse_proxy 127.0.0.1:15672
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
invalid ❓ This doesn't seem right
Projects
None yet
Development

No branches or pull requests

4 participants