caddy icon indicating copy to clipboard operation
caddy copied to clipboard

Confusing behaviour when starting a server on the admin port

Open arp242 opened this issue 6 months ago • 9 comments

With this:

% cat Caddyfile
http://localhost:2019 {
	file_server browse
}

% caddy run
2025/06/08 15:17:36.279 INFO    using adjacent Caddyfile
2025/06/08 15:17:36.279 INFO    adapted config to JSON  {"adapter": "caddyfile"}
2025/06/08 15:17:36.279 INFO    admin   admin endpoint started  {"address": "localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2025/06/08 15:17:36.280 INFO    tls.cache.maintenance   started background certificate maintenance      {"cache": "0xc00037da80"}
2025/06/08 15:17:36.280 WARN    http    HTTP/2 skipped because it requires TLS  {"network": "tcp", "addr": ":2019"}
2025/06/08 15:17:36.280 WARN    http    HTTP/3 skipped because it requires TLS  {"network": "tcp", "addr": ":2019"}
2025/06/08 15:17:36.280 INFO    http.log        server running  {"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2025/06/08 15:17:36.280 INFO    autosaved config (load with --resume flag)      {"file": "/home/martin/.config/caddy/autosave.json"}
2025/06/08 15:17:36.280 INFO    serving initial configuration
2025/06/08 15:17:36.281 INFO    tls     storage cleaning happened too recently; skipping for now        {"storage": "FileStorage:/home/martin/.local/share/caddy", "instance": "cf308ad4-1b49-40a8-aca4-dd95b219400e", "try_again": "2025/06/09 15:17:36.281", "try_again_in": 86399.99999964}
2025/06/08 15:17:36.281 INFO    tls     finished cleaning storage units
2025/06/08 15:17:39.664 INFO    admin.api       received request        {"method": "GET", "host": "localhost:2019", "uri": "/", "remote_ip": "127.0.0.1", "remote_port": "53406", "headers": {"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-IE,en;q=0.5"],"Connection":["keep-alive"],"Priority":["u=0, i"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Site":["none"],"Sec-Fetch-User":["?1"],"Upgrade-Insecure-Requests":["1"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:139.0) Gecko/20100101 Firefox/139.0"]}}

Going to http://localhost:2019 gives a 404.

The reason is that localhost:2019 is also used as an "admin port" by default. Simply using another port or admin off fixes it.

This is kind of obvious in hindsight, but not being familiar with Caddy this took me quite some time to figure out. I just saw port 2019 referenced somewhere and figured "well, that's a free port on my system, as good as any other".

Trying to use the admin port should probably give an error, or at least a big warning.

arp242 avatar Jun 08 '25 15:06 arp242

Ha, yeah I can see why that's confusing.

We can probably add a warning in the logs.

mholt avatar Jun 09 '25 16:06 mholt

Hi, it's my first time contributing to this project — would it be okay if I work on this issue?

I took a look at the Validate() method in caddy/modules/caddyhttp/app.go, and I believe a viable solution would be to add the current admin address to the lnAddrs map with a srvName like "admin". That way, port conflicts such as reusing :2019 can be detected earlier with a proper error message.

Would that be an acceptable approach? If so, could you advise how to access the current admin configuration (status and bind address) from within this method?

brecabral avatar Jun 09 '25 23:06 brecabral

That wouldn't work because admin is higher level than the caddyhttp app, caddyhttp does not know and should not know about admin.

It might need to be something more like a function near caddy.Listen() (i.e. listeners.go) which checks if a given port is available to use, so caddyhttp could call it to ask "can I use this port" during Validate. Something like CanListen(port int), since admin.go is in the same package it can reject using the admin port. Maybe too simplistic, I dunno, only spent 2 minutes thinking about it.

francislavoie avatar Jun 10 '25 04:06 francislavoie

Thanks for the feedback!

I'll proceed by forking the repository and creating a dedicated branch for this issue. My plan is to:

  1. Follow TDD: Start by writing a test that demonstrates the port conflict.
  2. Implement CanListen: Create the CanListen function as recommended.
  3. Integrate: Use this function to validate the port during Caddy's configuration loading.

I'll post here again if I encounter any specific questions or need clarification on implementation details. Once I have a functional branch, I'll open a draft PR to get early feedback.

Does this approach align with the project's best practices, or is there anything else I should be aware of before proceeding?

brecabral avatar Jun 10 '25 15:06 brecabral

Hi there!

I've opened a draft Pull Request (PR) related to this issue to kick off a discussion about Admin API port conflict validation.

The PR description details the proposed solution and my key architectural questions regarding the best way forward, especially concerning:

  1. How to robustly compare NetworkAddress objects.
  2. How to dynamically get the Admin API's configured address.
  3. Whether the proposed approach aligns with Caddy's design principles.

I'd really appreciate it if you could take a look and provide some guidance on the direction.

Thanks!

brecabral avatar Jun 11 '25 19:06 brecabral

Hi @francislavoie / @mholt ,

I'm new to Caddy and I was looking at this issue. I could finally sent a PR #7116. Please kindly review and let me know if any changes have to be made.

Thank you!

ksankeerth avatar Jul 10 '25 14:07 ksankeerth

Hi, can i work on this?

eshentials avatar Aug 11 '25 19:08 eshentials

Hi, can i work on this?

Can you please share your implementation plan?

mohammed90 avatar Aug 12 '25 04:08 mohammed90

Hi! I'm reproducing this issue and can confirm the problem exists, but it's actually even worse than described in the original report. My test setup:

Environment: Windows, latest master branch. Config: Same as original issue.

What I observed:

Server starts without any errors: INFO admin admin endpoint started {"address": "localhost:2019"} INFO http.log server running {"name": "srv0"}

But Admin API is completely broken: curl http://localhost:2019/config/ returns: 404 Not Found

HTTP server works instead: curl http://localhost:2019/ returns: HTML file browser (works fine)

The real problem: Admin API becomes completely inaccessible when HTTP server uses the same port.

Proposed solution: Add validation in replaceLocalAdminServer() that runs after the admin address is fully resolved (including CADDY_ADMIN env var) but before any servers start. The validation will:

  • Parse HTTP app configuration to get all server listen addresses
  • Use NetworkAddress.overlaps() method to detect conflicts (handles port ranges, localhost variants, wildcards). I will add this method.
  • Fail fast with clear error message identifying the conflicting server

Example error output: Error: admin port conflict: HTTP server 'srv0' listener address 'localhost:2019' conflicts with admin API address 'localhost:2019'

I'll add tests covering various scenarios: localhost vs 127.0.0.1, wildcards, port ranges, IPv6, environment overrides, etc.

Would appreciate feedback on this approach before implementation!

Siomachkin avatar Aug 29 '25 14:08 Siomachkin