[SOLVED] Cadddy v2 reverse proxy

1. My Caddy version (caddy version):

caddy_2.0.0-rc.3

2. How I run Caddy:

docker run --rm -it -p 8081:80 \
    -v $PWD/index.html:/usr/share/caddy/index.html \
    -v $PWD/caddy_data:/data \
    -v $PWD/Caddyfile:/etc/caddy/Caddyfile \
    caddy

System environment:

Docker version 19.03.8-ce, build afacb8b7f0
5.4.33-3-MANJARO x86_64 GNU/Linux
Description: Manjaro Linux
Release: 20.0
Codename: Lysia

My complete Caddyfile or JSON config:

{
	debug
}

http://localhost {
    templates
    file_server browse

    reverse_proxy /api/ {
	    to 192.168.1.2:8001
	}
}

3. The problem I’m having:

Hi guys,

My goal is to run Caddy as a Docker container on my Synology NAS to access other Docker containers. Caddy should be the entrypoint and act as a reverse proxy for the whole network of otherwise publicly inaccessible containers runnin on the Synology.

Specifically, I would like to hide backend services running on my Synology NAS (like Jitsi, private docker registry, etc.) and access them via Caddy using a dedicated domain names (e.g. jitsi.mydomain.com, registry.mydomain.com.

To come up with a working configuration, I have been testing this locally on my notebook but cannot seem to get the it right.

My Docker registry container is running on 192.168.1.2:8001 (no HTTPS - not imporant, this is just a testing service). If I use abovementioned configuration, URL is automatically redirected to http://localhost/repo/api. But as I am not sending any path info, I do not get where it gets the /repo/api part.

Now, my Docker registry container http://192.168.1.2:8001/repo/postgres-postgis does have format of URIs like http://192.168.1.2:8001/repo/postgres-postgis but I have no idea why the reverse proxy slaps /api/ at the very end of the path and how it even discovers the /repo/ part. If I visit the docker registry container address from my LAN, the frontend URI is just http://192.168.1.2:8001 without any path information or query strings.

4. Error messages and/or full log output:

I see the error message in the browser:

This localhost page can’t be foundNo webpage was found for the web address: http://localhost:8081/repo/api
HTTP ERROR 404

Caddy log:

020/04/24 18:28:35.375 INFO    using provided configuration    {"config_file": "/etc/caddy/Caddyfile", "config_adapter": "caddyfile"}
2020/04/24 18:28:35.380 INFO    admin   admin endpoint started  {"address": "tcp/localhost:2019", "enforce_origin": false, "origins": ["127.0.0.1:2019", "localhost:2019", "[::1]:2019"]}
2020/04/24 18:28:35.380 INFO    http    server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server {"server_name": "srv0", "http_port": 80}
2020/04/24 18:28:35 [INFO][cache:0xc0000e40f0] Started certificate maintenance routine
2020/04/24 18:28:35.380 INFO    tls     cleaned up storage units
2020/04/24 18:28:35.381 DEBUG   http    starting server loop    {"address": "[::]:80", "http3": false, "tls": false}
2020/04/24 18:28:35.381 INFO    autosaved config        {"file": "/config/caddy/autosave.json"}
2020/04/24 18:28:35.381 INFO    serving initial configuration
2020/04/24 18:28:37.534 DEBUG   http.handlers.reverse_proxy     upstream roundtrip      {"upstream": "192.168.1.2:8001", "request": {"method": "GET", "uri": "/api/", "proto": "HTTP/1.1", "remote_addr": "172.17.0.1:34834", "host": "localhost:8081", "headers": {"X-Forwarded-For": ["172.17.0.1"], "Upgrade-Insecure-Requests": ["1"], "Sec-Fetch-Site": ["cross-site"], "Sec-Fetch-Mode": ["navigate"], "Sec-Fetch-Dest": ["document"], "Accept-Encoding": ["gzip, deflate, br"], "Cache-Control": ["max-age=0"], "User-Agent": ["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36"], "Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"], "Sec-Fetch-User": ["?1"], "Accept-Language": ["en-US,en;q=0.9"], "Dnt": ["1"]}}, "headers": {"Cache-Control": ["no-cache"], "Content-Security-Policy": ["default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src 'self' https:; style-src 'self' https:"], "X-Request-Id": ["0edddc01-af5a-4f0e-9b3d-37ebf94a3014"], "X-Runtime": ["0.001439"], "Content-Length": ["96"], "Location": ["http://localhost:8081/repo/api"], "Content-Type": ["text/html"]}, "duration": 0.005111185, "status": 301}
2020/04/24 18:28:41.399 DEBUG   http.handlers.reverse_proxy     upstream roundtrip      {"upstream": "192.168.1.2:8001", "request": {"method": "GET", "uri": "/api/", "proto": "HTTP/1.1", "remote_addr": "172.17.0.1:34834", "host": "localhost:8081", "headers": {"Upgrade-Insecure-Requests": ["1"], "User-Agent": ["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36"], "Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"], "Sec-Fetch-Dest": ["document"], "Accept-Encoding": ["gzip, deflate, br"], "Dnt": ["1"], "Sec-Fetch-Site": ["none"], "Sec-Fetch-Mode": ["navigate"], "Sec-Fetch-User": ["?1"], "Accept-Language": ["en-US,en;q=0.9"], "X-Forwarded-For": ["172.17.0.1"]}}, "headers": {"Location": ["http://localhost:8081/repo/api"], "Content-Type": ["text/html"], "Cache-Control": ["no-cache"], "Content-Security-Policy": ["default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src 'self' https:; style-src 'self' https:"], "X-Request-Id": ["046edf4a-3909-4e11-83b2-1980a838fedc"], "X-Runtime": ["0.001500"], "Content-Length": ["96"]}, "duration": 0.0040336, "status": 301}

5. What I already tried:

I tried uri and rewrite before reverse_proxy stanza, but as I am not a Caddy expert, I did not get any useful results.

Thank you for your suggestions, guys.

It’s unclear to me where /repo is coming from. That said, in Caddy v2, path matchers are exact, so you need to use /api/* for your reverse_proxy to handle any API path.

It looks like your service at 192.168.1.2:8001 is returning the Location header triggering a redirect. Best check that app or its configuration.

Thank you for your answer, @francislavoie

I’d have to investigate what Location header that app sends. However in the meantime, is there a way to remove that on Caddy’s end? I have seen header_down Location. Would that help?

I have also tried

route /api* {
    uri strip_prefix api
    reverse_proxy  192.168.1.2:8001   
}

which was suggested here V2 reverse_proxy: please add CORS example to the docs - #22 by Frank_R. While that removed the /api part, it also breaks links and css style source addresses, so that is a no go.

I’m not sure that it would help. If the app is triggering a redirect, there’s typically a good reason for it (in this case the reason likely being misconfiguration). What application is it you’re proxying to in this case? Does it have examples for nginx you can refer to? Nginx configs typically end up working similarly to Caddy’s, except Caddy can usually get away with specifying a lot less options (shorter, cleaner configs).

That strip_prefix pattern is more typical for situations where users want to run some service under a subpath rather than a subdomain. I don’t think it’s the right thing for this situation.

Much appreciated you responding so quickly, really. As I am happy with running things on subdomains, I will not pursue this further. I just wanted to understand and know how to configure running things under subpaths as this might come handy in the future (as I am sure it will).

I am running this service as a simple Rails-based Docker registry frontend: Docker Hub

If I might, I’d have one last unrelated question: is it possible to transfer Letsencrypt’s certs already issued and used for my current nginx setup to Caddy? I don’t quite understand whether Caddy will somehow find out through LE ACME server that the domains have already certs issued and get them from LE or whether I need to physically copy them over from my Debian-based server to Synology and mount them somehow in docker-compose.

Thank you kindly.

If the only thing you’re trying to serve is that aoo, then I think a simple reverse proxy should be enough for this site, shouldn’t it? I’m not sure what else you’re trying to serve, so it’s unclear what’s happening.

http://localhost {
    reverse_proxy 192.168.1.2:8001
}

Their docs mention some instructions if running under a subpath:

So you could run this under a subpath if you prefer, as long as you set those env vars when running it. It would look like this:

reverse_proxy /browser/* 192.168.1.2:8001

Note that for when you bring this on with HTTPS, their docs mention:

If you’re using a reverse-proxy setup with SSL termination in front of this application in combination with ENABLE_DELETE_IMAGES=true you must make sure that the application knows about this fact (by sending X-Forwarded-Proto: https in the HTTP headers).

Currently Caddy doesn’t sent that header automatically, but it will as of the next release (you can add it yourself with the header_up subdirective of reverse_proxy in the meantime):

This isn’t necessary, Caddy will fetch and manage its own certificates. Using Let’s Encrypt, it’s not necessary to reuse certificates from old installations. The rate limits are quite generous. It would be more work to try and move the certificates to the exact paths Caddy would expect and even then the contents might not be exactly correct either (wrong key types, etc).

1 Like

If the only thing you’re trying to serve is that aoo, then I think a simple reverse proxy should be enough for this site, shouldn’t it? I’m not sure what else you’re trying to serve, so it’s unclear what’s happening.

http://localhost {
    reverse_proxy 192.168.1.2:8001
}

I tried that in the meantime and it works very nicely. I am quite certain the subpath problem is related to those Rails settings you found. Silly me for overlooking it :see_no_evil:

Amazing support, Francis. Thank you very much for such helpful attitude. I see I’ve made a very good decision to switch to Caddy!

2 Likes

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