Caddy 2 -- connection refused error in WSL2 (Debian 11)

1. Output of caddy version:

v2.5.2 h1:eCJdLyEyAGzuQTa5Mh3gETnYWDClo1LjtQm2q9RNZrs=

2. How I run Caddy:

a. System environment:

WSL2 on Windows 11. Debian 11 (Bullseye) distro.

b. Command:

$ caddy reload --config ~/Caddyfile

c. Service/unit/compose file:


ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile --force


d. My complete Caddy config:

localhost {
        root * ~/programming/php
        encode gzip
        php_fastcgi localhost:9000

3. The problem I’m having:

I’m attempting to set up a Caddy 2 server in my WSL2 Debian environment in order to do some development work. Despite WSL2 not having systemd support, Caddy still works via the command line (e.g., $ caddy start, $ caddy stop, $ caddy reload, etc.).

When I try to run Caddy with my simple Caddyfile, I get a “connection refused” error.

4. Error messages and/or full log output:

2022/08/28 19:16:18.411 INFO    using provided configuration    {"config_file": "/home/night/Caddyfile", "config_adapter": ""}
2022/08/28 19:16:18.412 INFO    using provided configuration    {"config_file": "/home/night/Caddyfile", "config_adapter": ""}
reload: sending configuration to instance: performing request: Post "http://localhost:2019/load": dial tcp connect: connection refused

5. What I already tried:

I’m new to both WSL2 and Caddy, so I really have no idea what to do

6. Links to relevant resources:

Honestly, I’m not too sure what to suggest. WSL does all kinds of funky stuff with networking.

But for context, localhost:2019 is Caddy’s admin endpoint. Using caddy reload attempts to push the new config to the admin endpoint. So if Caddy isn’t running, or the admin endpoint is turned off, then it won’t be possible to reload.

I feel like I’ve made a bit of progress. I’ve opened up ports 2019 and 443 in my Windows Firewall. I also moved my Caddyfile from my linux home directory to /etc/caddy/Caddyfile mainly because that’s where it really should go. My current error is now:

2022/08/28 20:44:39.219 INFO    using provided configuration    {"config_file": "/etc/caddy/Caddyfile", "config_adapter": ""}
2022/08/28 20:44:39.220 INFO    using provided configuration    {"config_file": "/etc/caddy/Caddyfile", "config_adapter": ""}
2022/08/28 20:44:39.221 INFO    admin.api       received request        {"method": "POST", "host": "localhost:2019", "uri": "/load", "remote_ip": "", "remote_port": "54460", "headers": {"Accept-Encoding":["gzip"],"Content-Length":["937"],"Content-Type":["application/json"],"Origin":["http://localhost:2019"],"User-Agent":["Go-http-client/1.1"]}}
2022/08/28 20:44:39.221 INFO    admin   admin endpoint started  {"address": "tcp/localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//"]}
2022/08/28 20:44:39.221 INFO    http    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}
2022/08/28 20:44:39.221 INFO    http    enabling automatic HTTP->HTTPS redirects        {"server_name": "srv0"}
2022/08/28 20:44:39.221 INFO    tls.cache.maintenance   started background certificate maintenance      {"cache": "0xc000192c40"}
2022/08/28 20:44:39.222 INFO    root certificate is already trusted by system   {"path": "storage:pki/authorities/local/root.crt"}
2022/08/28 20:44:39.222 INFO    tls.cache.maintenance   stopped background certificate maintenance      {"cache": "0xc000192c40"}
2022/08/28 20:44:39.222 ERROR   admin.api       request error   {"error": "loading config: loading new config: http app module: start: tcp: listening on :80: listen tcp :80: bind: permission denied", "status_code": 400}
reload: sending configuration to instance: caddy responded with error: HTTP 400: {"error":"loading config: loading new config: http app module: start: tcp: listening on :80: listen tcp :80: bind: permission denied"}

2022/08/28 20:44:39.222 INFO    admin   stopped previous server {"address": "tcp/localhost:2019"}

You also need port 80 for Caddy to perform HTTP->HTTPS redirects.

Yeah, I noticed that and opened it, but the same error re: bind persists. A bit of research tells me this is because I’m not running caddy via systemd… is there a way to force it to accept low ports without systemd?

EDIT: I’m trying to get this to work because I’m pretty sure it’ll benefit others who would like to use caddy in a WSL2 development environment. Especially if it can be done pseudo-officially without resorting to systemd-genie. If that’s required, so be it, but it’d be cool to have a mostly out-of-the-box solution

It might be possible to give the Caddy binary the capability to bind to low ports even when not running as a privileged user:

sudo setcap cap_net_bind_service=+ep /usr/bin/caddy

I have no idea if this works in WSL though. I don’t know what kind of limitations it sets compared to regular Linux for things like this.

Doesn’t seem to work, unfortunately. Same error.

I’m going to try installing systemd-genie later this week (when I have time) and see if that does the trick. I’ll post any new successes or failures here. Thanks for all your help so far!

If you don’t actually need HTTPS support, or need to use low ports, you could configure Caddy like this to listen on a high port over HTTP only:

:8080 {
	root * ~/programming/php
	encode gzip
	php_fastcgi localhost:9000

Caddy works well on Windows directly, you could run two instances if all else fails, the one on Windows to reverse_proxy to the one inside WSL. Make sure to configure trusted_proxies private_ranges inside of your php_fastcgi config if you take this approach though, to make sure X-Forwarded-* headers still work.

1 Like

Success! I was able to get it working with systemd-genie. Followed the instructions here: GitHub - arkane-systems/genie: A quick way into a systemd "bottle" for WSL I can use all systemctl commands now


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