Reverse Proxy with rewrite of path, stripping parts

1. Caddy version (caddy version): 2.4.1

2. How I run Caddy:

a. System environment:

Docker 20.10.7 on Ubuntu 18.04

b. Command:

docker-compose -f docker-compose.yml -f docker-compose.azurevm-highperf-caddy.yml up

c. Service/unit/compose file:

docker-compose.yml:

version: "2"

services:
  elasticsearch:
    build:
      context: elasticsearch/
    volumes:
      - elasticsearch-data:/usr/share/elasticsearch/data
      - ./elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro
    environment:
      node.name: elasticsearch
      cluster.initial_master_nodes: elasticsearch
      ES_CLUSTER_NAME: search-cluster
      ES_DATA_DIR: /usr/share/elasticsearch/data
    networks:
      - elk

volumes:
  elasticsearch-data:
    driver: local

networks:
  elk:
    driver: bridge

docker-compose.azurevm-highperf-caddy.yml:

version: "2"

services:
  elasticsearch:
    restart: always
    environment:
      ES_JAVA_OPTS: "-Xmx4000m -Xms4000m"

  caddy:
    image: caddy:2.4.1
    container_name: caddy
    restart: always
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy-config:/config
      - ./caddy-data:/data
    environment:
      - DOMAIN=adv-es-https-test-1.westeurope.cloudapp.azure.com
      - EMAIL=a@skwar.me
      - LOG_FILE=/data/access.log
    ports:
      - 80:80
      - 443:443
    networks:
      - elk
    depends_on:
      - elasticsearch

d. My complete Caddyfile or JSON config:

{$DOMAIN}:443

route {
        rewrite /es/ /
        reverse_proxy http://elasticsearch:9200
}

3. The problem I’m having:

I’d like Caddy to be a reverse proxy to Elasticsearch. It should only reverse proxy paths beginning with /es/ and it should remove the /es/ from the path when it gets send to http://elasticsearch:9200. So, when eg. https://adv-es-https-test-1.westeurope.cloudapp.azure.com/es/foo is called, it should be proxied to http://elasticsearch:9200/foo.

4. Error messages and/or full log output:

With the above Caddyfile, going to https://adv-es-https-test-1.westeurope.cloudapp.azure.com/es/ seems to work. I get a response from Elasticsearch.

$ curl https://adv-es-https-test-1.westeurope.cloudapp.azure.com:443/es/foo
{"error":"Incorrect HTTP method for uri [/es/foo] and method [GET], allowed: [POST]","status":405}

$ curl https://adv-es-https-test-1.westeurope.cloudapp.azure.com:443/foo
{"error":{"root_cause":[{"type":"index_not_found_exception","reason":"no such index [foo]","resource.type":"index_or_alias","resource.id":"foo","index_uuid":"_na_","index":"foo"}],"type":"index_not_found_exception","reason":"no such index [foo]","resource.type":"index_or_alias","resource.id":"foo","index_uuid":"_na_","index":"foo"},"status":404}

When I compare these two invocations, it seems that in the first invocation (with /es/foo), the /es did not get stripped.

5. What I already tried:

It seems that the route { ... } part isn’t required. Leaving it out doesn’t seem to make a difference.
I also tried simplyfying the Caddyfile to this:

{$DOMAIN}:443

reverse_proxy /es/* http://elasticsearch:9200

It then does the reverse proxy fine, but doesn’t remove the /es/ from the beginning of the path:

$ curl https://adv-es-https-test-1.westeurope.cloudapp.azure.com:443/es/
{"error":{"root_cause":[{"type":"index_not_found_exception","reason":"no such index [es]","resource.type":"index_or_alias","resource.id":"es","index_uuid":"_na_","index":"es"}],"type":"index_not_found_exception","reason":"no such index [es]","resource.type":"index_or_alias","resource.id":"es","index_uuid":"_na_","index":"es"},"status":404}

6. Links to relevant resources:

You’re either looking for uri (Caddyfile directive) — Caddy Documentation or for handle_path (Caddyfile directive) — Caddy Documentation to strip the path prefix.

But generally, I’d recommend using subdomains for each service, instead of subpaths. See this article for an explanation:

2 Likes

Thanks a lot. It works quite well using handle_path, like so:

handle_path /elasticsearch/* {
        basicauth bcrypt Elasticsearch {
                Bob JDJhJDEwJEVCNmdaNEg2Ti5iejRMYkF3MFZhZ3VtV3E1SzBWZEZ5Q3VWc0tzOEJwZE9TaFlZdEVkZDhX
        }

        reverse_proxy http://elasticsearch:9200
}

While I agree that subdomains might be better, this isn’t feasible in the current situation for me.

Oh, I just noticed, that https://adv-es-https-test-1.westeurope.cloudapp.azure.com/elasticsearch does not work. It only works with a trailing slash.

I changed the Caddyfile to this:

handle_path /elasticsearch {
        basicauth bcrypt Elasticsearch {
                Bob JDJhJDEwJEVCNmdaNEg2Ti5iejRMYkF3MFZhZ3VtV3E1SzBWZEZ5Q3VWc0tzOEJwZE9TaFlZdEVkZDhX
        }

        reverse_proxy http://elasticsearch:9200
}

But then https://adv-es-https-test-1.westeurope.cloudapp.azure.com/elasticsearch works, but https://adv-es-https-test-1.westeurope.cloudapp.azure.com/elasticsearch/foo-bar does not.

Do I need to have two handle_path blocks?

You still need the * even if you remove the /. Path matching is exact in Caddy, so /elasticsearch only matches exactly /elasticsearch and nothing else.

1 Like

Great!

This works:

handle_path /elasticsearch* {
        basicauth bcrypt Elasticsearch {
                Bob JDJhJDEwJEVCNmdaNEg2Ti5iejRMYkF3MFZhZ3VtV3E1SzBWZEZ5Q3VWc0tzOEJwZE9TaFlZdEVkZDhX
        }

        reverse_proxy http://elasticsearch:9200
}
1 Like

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