"Host not allowed" when calling the API remotely

1. My Caddy version (caddy version):

~ # ./caddy version                                                                                                                                                            
v2.0.0-rc.3 h1:z2H/QnaRscip6aZJxwTbghu3zhC88Vo8l/K57WUce4Q=

2. How I run Caddy:

~ # ./caddy run                                                                                                                                                                
2020/04/23 08:19:05.502 INFO    admin   admin endpoint started  {"address": "tcp/localhost:2019", "enforce_origin": false, "origins": ["localhost:2019", "[::1]:2019", ""]}
2020/04/23 08:19:05.502 INFO    serving initial configuration

a. System environment:

Ubuntu 18.04

d. My complete Caddyfile or JSON config:

None (see below)

3. The problem I’m having:

When trying to access a modified API endpoint (see below for the fill scenario) I get

{"error":"host not allowed:"}

5. What I already tried:

I started Caddy without any configuration caddy run and then pushed a new configuration via the API so that the admin endpoint is available outside of localhost:

~ # curl localhost:2019/load \                                                                                                                                                 root@srv
        -X POST \
    -H "Content-Type: application/json" \
    -d @- << EOF
"admin": {
    "listen": ":2020"

This modifies the endpoint on the server, as seen in its logs:

2020/04/23 08:19:20.828 INFO    admin.api       received request        {"method": "POST", "host": "localhost:2019", "uri": "/load", "remote_addr": "", "headers": {"Accept":["*/*"],"Content-Length":["42"],"Content-Type":["application/json"],"User-Agent":["curl/7.58.0"]}}
2020/04/23 08:19:20.832 INFO    admin   admin endpoint started  {"address": "tcp/:2020", "enforce_origin": false, "origins": [":2020"]}
2020/04/23 08:19:20.832 INFO    autosaved config        {"file": "/root/.config/caddy/autosave.json"}
2020/04/23 08:19:20.832 INFO    admin.api       load complete
2020/04/23 08:19:21.333 INFO    admin   stopped previous server

I now tried to connect to that endpoint from anywhere (including localhost) and I get the follwing error message:

{"error":"host not allowed:"}

On the server side, the logs agree:

2020/04/23 08:19:26.594 INFO    admin.api       received request        {"method": "GET", "host": "localhost:2020", "uri": "/config/", "remote_addr": "", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.58.0"]}}
2020/04/23 08:19:26.595 ERROR   admin.api       request error   {"error": "host not allowed: localhost:2020", "status_code": 403}
2020/04/23 08:37:05.888 INFO    admin.api       received request        {"method": "GET", "host": "", "uri": "/config/", "remote_addr": "", "headers": {"Accept":["*/*"],"User-Agent":["curl/7.55.1"]}}
2020/04/23 08:37:05.888 ERROR   admin.api       request error   {"error": "host not allowed:", "status_code": 403}

Where should I define which IP addresses (or ranges) are allowed to access the API?

I also tried to put an actual IP address for the interface ( instead of :2020) but the problem is the same.

EDIT: it would also seem that the API access should be open: v2: Access controls for admin API · Issue #2850 · caddyserver/caddy · GitHub

I had a look at the relevant piece of code

// checkHost returns a handler that wraps next such that
// it will only be called if the request's Host header matches
// a trustworthy/expected value. This helps to mitigate DNS
// rebinding attacks.
func (h adminHandler) checkHost(r *http.Request) error {
	var allowed bool
	for _, allowedHost := range h.allowedOrigins {
		if r.Host == allowedHost {
			allowed = true
	if !allowed {
		return APIError{
			Code: http.StatusForbidden,
			Err:  fmt.Errorf("host not allowed: %s", r.Host),
	return nil

From that I derive that the host IP (the host caddy runs on) must be in origins. I tried

    "admin": {
        "listen": ":2020",
        "origins": [""],
        "enforce_origin": false

and it works!

Since the docs say that origin is not used when enforce_origin is true, I guess that this should be changed in the documentation (if my analysis is correct).

One drawback of this solution is that I have to explicitly set the IP I will bind to (in origin, but not in listen).

@WoJ RC3 doesn’t have the latest fixes from about a week ago: admin: Disable host checking if wildcard interface is specified · caddyserver/caddy@f5ccb90 · GitHub - the website also doesn’t have the latest docs, which will be updated when 2.0 is released.

Thanks Matt, this is good news!

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