Same reverse proxy for HTTP and websocket request

1. Caddy version (caddy version):

I’m using Docker caddy:2.1.1

2. How I run Caddy:

I’m running Caddy using docker-compose with this config:

frontend:
    image: caddy:2.1.1-alpine
    volumes:
      - ./build:/usr/share/caddy
      - ./Caddyfile:/etc/caddy/Caddyfile
    ports:
      - 3000:3000
      - 3001:443
    networks: 
      - my_network

build is the destination directory of React (HTML and JS files)

a. System environment:

I’m running it on Mac using docker-compose

b. Command:

# described above
docker-compose up -d frontend

c. Service/unit/compose file:

version: "3.8"

services:
  frontend:
    image: caddy:2.1.1-alpine
    volumes:
      - ./build:/usr/share/caddy
      - ./Caddyfile:/etc/caddy/Caddyfile
    ports:
      - 3000:3000
      - 3001:443
    networks: 
      - mynetwork
  

  backend:
    build:
      context: ./backend
    ports:
      - 8000:8000
    environment:
      - DATABASE_URL=postgres://******
    networks: 
      - mynetwork

  postgres:
    image: postgres:13.0
    environment:
      - POSTGRES_USER=****
      - POSTGRES_PASSWORD=***
      - POSTGRES_DB=****
    ports:
      - 5432:5432
    networks: 
      - mynetwork

networks:
  mynetwork:
    driver: bridge

d. My complete Caddyfile or JSON config:

I’m using Caddyfile

localhost:3000 {
	root * /usr/share/caddy
	@websockets {
		header Connection *Upgrade*
		header Upgrade    websocket
	}
	reverse_proxy @websockets localhost:8000/graphql

	route {
		reverse_proxy /graphql backend:8000
		try_files {path} {path}/ /index.html?{query}
		file_server
	}

	log {
		output stdout
	}
}

3. The problem I’m having:

Hi :slight_smile:

I’m trying to deploy React app using GraphQL backend that support subscription through Web Sockets.
The backend has only one route /graphql where client is using HTTP and WebSocket requests. I want to serve the app using Caddy and redirect all “api requests” to backend micro-service.

The HTTP requests went well but no matter what I tried I didn’t find a way to serve Websocket.
I’m getting on Chrome:

client.ts:557 WebSocket connection to 'ws://localhost:3000/graphql' failed: Error during WebSocket handshake: Unexpected response code: 400

and can’t find relevant log in Caddy

4. Error messages and/or full log output:

No error messages in Caddy output

5. What I already tried:

Couldn’t find in the web neither this forum a solution

6. Links to relevant resources:

no relevant resources

This line is the problem:

reverse_proxy @websockets localhost:8000/graphql

You can’t proxy to a subpath in Caddy v2, I think you should just remove /graphql from that line. (If you needed to do that, you’d need to do a rewrite before proxying, but I don’t think it’s necessary here)

Also, you wrote localhost:8000, so that will be trying to connect to a service on port 8000 inside the same container as Caddy. I think you meant backend:8000 here, same as the other proxy.

Hi, thanks a lot for your comment.
I changed the “localhost” to “backend” probably forgot to change it after few tries. I also tried to add rewrite line like this: rewrite @websockets /graphql but it wasn’t help too :frowning: .

I run tcpdump on the Caddy machine to better understand what’s going on there and it seems Caddy is not transfer the UPGRADE request to the backend service at all (there is no try to connect to the server).


(the dump is for all port and devices in the machine at the time Chrome is trying to reconnect using websocket)

Any suggestion? I’m helpless

What’s your full Caddyfile at this point? What’s in the logs?

@francislavoie thanks for the fast reply!
long story short - it was my bad not related to Caddy at all. I used “ws://…” url instead of “wss://…” at my app (or not using SSL at all).

I think I solved it :slight_smile: If I’m running Caddy on port 80 (no SSL) then it’s work.
If I’m using HTTPS it doesn’t. Do I need to make my backend work with ssl for it to work too?

my full Caddyfile is:

localhost:80 {
	root * /usr/share/caddy

	route {
		reverse_proxy /graphql backend:8000
		try_files {path} {path}/ /index.html?{query}
		file_server
	}

	log {
		output stdout
	}
}

I changed my docker-compose file accordingly:

frontend:
    image: caddy:2.1.1-alpine
    volumes:
      - ./build:/usr/share/caddy
      - ./Caddyfile:/etc/caddy/Caddyfile
    ports:
      - 3000:80

Caddy 5 last logs lines are the same (I masked my cookies…):

frontend_1  | {"level":"info","ts":1603574063.1167455,"logger":"http.log.access.log0","msg":"handled request","request":{"method":"GET","uri":"/graphql","proto":"HTTP/1.1","remote_addr":"172.23.0.1:47246","host":"localhost:3000","headers":{"Connection":["Upgrade"],"Pragma":["no-cache"],"Sec-Websocket-Protocol":["graphql-ws"],"Upgrade":["websocket"],"Sec-Websocket-Version":["13"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Websocket-Extensions":["permessage-deflate; client_max_window_bits"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36"],"Origin":["http://localhost:3000"],"Accept-Language":["en-IL,en;q=0.9,he-IL;q=0.8,he;q=0.7,en-US;q=0.6"],"Cookie":*****,"Sec-Websocket-Key":["*******"],"Cache-Control":["no-cache"]}},"common_log":"172.23.0.1 - - [24/Oct/2020:21:14:23 +0000] \"GET /graphql HTTP/1.1\" 0 0","duration":0.0194891,"size":0,"status":0,"resp_headers":{"Server":["Caddy"]}}

No, Caddy will terminate TLS, so the proxy request will be HTTP (unless you configure it to make a TLS connection, which is unnecessary overhead for an connection to another docker container).

If you plan on using Automatic HTTPS, please don’t forget to persist the /data and /config volumes on your Caddy service. See the docs: Docker Hub

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