Reverse proxy return 200 with empty reponse body

1. The problem I’m having:

I created a docker-compose file with angular, .net core API, and caddy as a reverse proxy.

I can get the HTML from the angular app.

The problem is when I try to hit any endpoint from the API, it returns a 200 OK response but with an “empty” response body.

So. The API exposes ports 5001:5001. If I hit https://localhost:5001/api/securoty/login with the credentials it returns a json response, however, if I hit https://api.localhost/api/securoty/login the API response has a 200 OK with an empty response body.

curl -vL ‘https://api.localhost/security/login’ --header ‘Content-Type: application/json’ --data-raw ‘{“email”: “user@gmail.com”,“password”: “Password#”}’

Result:

  • Host api.localhost:443 was resolved.
  • IPv6: ::1
  • IPv4: 127.0.0.1
  • Trying [::1]:443…
  • Connected to api.localhost (::1) port 443
  • schannel: disabled automatic use of client certificate
  • ALPN: curl offers http/1.1
  • schannel: next InitializeSecurityContext failed: Unknown error (0x80096004) - The signature of the certificate cannot be verified.
  • Closing connection
  • schannel: shutting down SSL/TLS connection with api.localhost port 443
    curl: (35) schannel: next InitializeSecurityContext failed: Unknown error (0x80096004) - The signature of the certificate cannot be verified.

2. Error messages and/or full log output:

Http code 200 : blank page

3. Caddy version:

2.8.4-alpine

4. How I installed and ran Caddy:

a. System environment:

Windows 11 Pro - Docker compose

b. Command:

docker compose up -d

d. My complete Caddy config:

{
	debug
}

admin.localhost {
	tls internal

	reverse_proxy floreria-admin:4200
}

api.localhost {

	reverse_proxy /api/* floreria-api:5001

	log {
		format console
		output stdout
	}
}

5. Links to relevant resources:

Here is my docker compose file

services:
    caddy:
        image: caddy:2.8.4-alpine
        container_name: caddy
        ports:
            - "80:80"
            - "443:443"
            - "443:443/udp"
        volumes:
            - ./Caddyfile:/etc/caddy/Caddyfile
            - caddy_data:/data
            - caddy_config:/config
        depends_on:
            - floreria-api
            - floreria-admin


    floreria-admin:
        build: 
            dockerfile: ./floreria-admin/Dockerfile
            context: .        
        container_name: floreria.admin
        ports:
            - "4200"     
        depends_on:
            - floreria-api


    floreria-api:
        build:
            dockerfile: ./Backend/Floreria.Api/Dockerfile
            context: .
        environment:
            - ASPNETCORE_ENVIRONMENT=Development            
            - ASPNETCORE_Kestrel__Certificates__Default__Password=Patito10
            - ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx
        container_name: floreria.api
        ports:
            - "5001:5001"
        volumes:
            - ./temp-keys/:/root/.aspnet/DataProtection-Keys
            - ./certificate/.aspnet/https:/https:ro
        depends_on:
            - floreria-db    


    floreria-db:
        image: mcr.microsoft.com/mssql/server:2022-latest
        container_name: floreria.db
        environment:
            - ACCEPT_EULA=Y
            - SA_PASSWORD=Patito10#
        ports:
          - "5002:1433"

volumes:
    caddy_data:
    caddy_config:

Howdy @Beto_Ramirez, welcome to the Caddy community.

To get an idea of what’s actually going on, we’ll need the actual log output.

Please retrieve the debug logs for Caddy from when you made the requests and post them.

1 Like

This is the log I get when I hit the API endpoint

2. Error messages and/or full log output:

2024-09-18 23:20:39 2024/09/19 05:20:39.885     DEBUG   http.stdlib     http: TLS handshake error from 172.18.0.1:54238: EOF
2024-09-18 23:20:39 2024/09/19 05:20:39.886     DEBUG   events  event   {"name": "tls_get_certificate", "id": "aa67a440-36b2-49e4-a4e0-29d90697480d", "origin": "tls", "data": {"client_hello":{"CipherSuites":[4865,4866,4867,49199,49195,49200,49196,49191,52393,52392,49161,49171,49162,49172,156,157,47,53],"ServerName":"api.localhost","SupportedCurves":[29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[1027,2052,1025,1283,2053,1281,2054,1537,513],"SupportedProtos":null,"SupportedVersions":[772,771,770,769],"RemoteAddr":{"IP":"172.18.0.1","Port":54242,"Zone":""},"LocalAddr":{"IP":"172.18.0.5","Port":443,"Zone":""}}}}
2024-09-18 23:20:39 2024/09/19 05:20:39.887     DEBUG   tls.handshake   choosing certificate    {"identifier": "api.localhost", "num_choices": 1}
2024-09-18 23:20:39 2024/09/19 05:20:39.887     DEBUG   tls.handshake   default certificate selection results   {"identifier": "api.localhost", "subjects": ["api.localhost"], "managed": true, "issuer_key": "local", "hash": "d12976b00916568f27af4c172576057bb3d692ff1d3084fe80ec39215f4c3acf"}
2024-09-18 23:20:39 2024/09/19 05:20:39.887     DEBUG   tls.handshake   matched certificate in cache    {"remote_ip": "172.18.0.1", "remote_port": "54242", "subjects": ["api.localhost"], "managed": true, "expiration": "2024/09/19 12:05:55.000", "hash": "d12976b00916568f27af4c172576057bb3d692ff1d3084fe80ec39215f4c3acf"}
2024-09-18 23:20:39 2024/09/19 05:20:39.896     INFO    http.log.access.log1    NOP     {"request": {"remote_ip": "172.18.0.1", "remote_port": "54242", "client_ip": "172.18.0.1", "proto": "HTTP/1.1", "method": "POST", "host": "api.localhost", "uri": "/security/login", "headers": {"Accept-Encoding": ["gzip, deflate, br"], "Connection": ["keep-alive"], "Content-Length": ["76"], "Content-Type": ["application/json"], "User-Agent": ["PostmanRuntime/7.42.0"], "Accept": ["*/*"], "Postman-Token": ["9e5d4c0d-2601-49d1-a7e7-746d2e9ce7bc"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "", "server_name": "api.localhost"}}, "bytes_read": 0, "user_id": "", "duration": 0.000183185, "size": 0, "status": 0, "resp_headers": {"Alt-Svc": ["h3=\":443\"; ma=2592000"], "Server": ["Caddy"]}}

See that "uri": "/security/login"?

This request doesn’t match /api/* so it doesn’t get handled by your reverse_proxy.

That means there is no handler configured which can produce a response, so Caddy returns a blank response. (It handled the HTTP request just fine and did not run into any errors, but was not configured to do anything in particular for this route.)

1 Like

I modified the url section and included “api” to URL, after that I got an 502 error Bad gateway.

Then, I modified the caddy file to the reverse proxy section.
changed
reverse_proxy /api/* floreria-api:5001

to
reverse_proxy /api floreria-api:5001

api.localhost {

	reverse_proxy /api floreria-api:5001

	log {
		format console
		output stdout
	}

}

after this change, I am still getting a successful response with an empty response body

2024-09-19 08:28:37 2024/09/19 14:28:37.129     INFO    http.log.access.log1    NOP     {"request": {"remote_ip": "172.18.0.1", "remote_port": "54248", "client_ip": "172.18.0.1", "proto": "HTTP/1.1", "method": "POST", "host": "api.localhost", "uri": "/api/security/login", "headers": {"Content-Length": ["76"], "Content-Type": ["application/json"], "User-Agent": ["PostmanRuntime/7.42.0"], "Accept": ["*/*"], "Postman-Token": ["08afc430-ba6c-48c5-a595-150e2ede4fca"], "Accept-Encoding": ["gzip, deflate, br"], "Connection": ["keep-alive"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "", "server_name": "api.localhost"}}, "bytes_read": 0, "user_id": "", "duration": 0.000009392, "size": 0, "status": 0, "resp_headers": {"Alt-Svc": ["h3=\":443\"; ma=2592000"], "Server": ["Caddy"]}}

Path matchers are literal.

This will match only requests to /api and not to /api/foo, /api/, or anything else.

You probably wanted to keep /api/* or maybe swap to /api* (although the latter would match /apifoo/bar for example).

1 Like

I revert the last change to the caddy file:

api.localhost {

	reverse_proxy /api* floreria-api:5001

	log {
		format console
		output stdout
	}

}

When I try to hit this endpoint https://api.localhost/security/login

I got this from log:

2024-09-19 13:14:42 2024/09/19 19:14:42.051     DEBUG   http.stdlib     http: TLS handshake error from 172.18.0.1:40108: EOF
2024-09-19 13:14:42 2024/09/19 19:14:42.052     DEBUG   events  event   {"name": "tls_get_certificate", "id": "f04a9a18-4c1b-4135-a063-bf34be5154ef", "origin": "tls", "data": {"client_hello":{"CipherSuites":[4865,4866,4867,49199,49195,49200,49196,49191,52393,52392,49161,49171,49162,49172,156,157,47,53],"ServerName":"api.localhost","SupportedCurves":[29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[1027,2052,1025,1283,2053,1281,2054,1537,513],"SupportedProtos":null,"SupportedVersions":[772,771,770,769],"RemoteAddr":{"IP":"172.18.0.1","Port":40112,"Zone":""},"LocalAddr":{"IP":"172.18.0.5","Port":443,"Zone":""}}}}
2024-09-19 13:14:42 2024/09/19 19:14:42.052     DEBUG   tls.handshake   choosing certificate    {"identifier": "api.localhost", "num_choices": 1}
2024-09-19 13:14:42 2024/09/19 19:14:42.052     DEBUG   tls.handshake   default certificate selection results   {"identifier": "api.localhost", "subjects": ["api.localhost"], "managed": true, "issuer_key": "local", "hash": "e6b5831fbbde6c4182ba2b556d3da82e144e9a50be91ce7142ab41ad81ca242a"}
2024-09-19 13:14:42 2024/09/19 19:14:42.052     DEBUG   tls.handshake   matched certificate in cache    {"remote_ip": "172.18.0.1", "remote_port": "40112", "subjects": ["api.localhost"], "managed": true, "expiration": "2024/09/20 07:10:02.000", "hash": "e6b5831fbbde6c4182ba2b556d3da82e144e9a50be91ce7142ab41ad81ca242a"}
2024-09-19 13:14:42 2024/09/19 19:14:42.054     INFO    http.log.access.log1    NOP     {"request": {"remote_ip": "172.18.0.1", "remote_port": "40112", "client_ip": "172.18.0.1", "proto": "HTTP/1.1", "method": "POST", "host": "api.localhost", "uri": "/security/login", "headers": {"Content-Length": ["76"], "Content-Type": ["application/json"], "User-Agent": ["PostmanRuntime/7.42.0"], "Accept": ["*/*"], "Postman-Token": ["60caa590-bf09-4d19-bf9c-6d33acf9486d"], "Accept-Encoding": ["gzip, deflate, br"], "Connection": ["keep-alive"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "", "server_name": "api.localhost"}}, "bytes_read": 0, "user_id": "", "duration": 0.000014056, "size": 0, "status": 0, "resp_headers": {"Server": ["Caddy"], "Alt-Svc": ["h3=\":443\"; ma=2592000"]}}

I am still getting an HTTP Code 200 with blank respose

That’s because you’re making requests to /security/login but only have handling for /api/*.

Why not just try reverse_proxy floreria-api:5001 with no matcher?

2 Likes

I tried reverse_proxy floreria-api:5001 with no matcher

and it works if I specify the port in the URL:

https://api.localhost:5001/api/security/login
I wonder if it’s possible to avoid specifying the port in the URL.

I would like to make the API not accessible outside of docker and the only way to request it is through the UI

Then you’re bypassing Caddy entirely. Caddy isn’t listening on port 5001.

Then remove this:

2 Likes

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