Enforce HTTP 1.1 for WebSockets endpoint

1. The problem I’m having:

I’m in the process of migrating from nginx to Caddy; with nginx we used to have these settings for our websockets endpoint:

        location /ws {
            proxy_pass http://api;
            proxy_http_version 1.1;
            proxy_set_header Upgrade         $http_upgrade;
            proxy_set_header Connection      "Upgrade";
            proxy_set_header Host            $host;
            proxy_set_header X-Forwarded-For $remote_addr;
        }

I can’t figure out how to enforce HTTP 1.1 for that particular endpoint in Caddy. Not using HTTP1.1 here results in getting the 101 status - and fixing it in the frontend app (sending the WS requests in HTTP 1.1) is not possible ATM. I tried adding this block

    transport http {
        versions 1.1
    }

but that doesn’t seem to change anything.

Is there any equivalent of proxy_http_version in Caddy? is it possible to enforce HTTP1.1 globally as a workaround?

2. Error messages and/or full log output:

There are no error messages - except for unwanted behavior of returning status 101 due to mixing HTTP/2 and HTTP/1.1.

3. Caddy version:

v2.10.0

4. How I installed and ran Caddy:

We’re using Docker - nothing was added much besides these lines in Dockerfile:

FROM caddy:2.10.0

COPY Caddyfile /etc/caddy/Caddyfile

a. System environment:

Docker

b. Command:

default one from the Docker image.

c. Service/unit/compose file:

  ingress-caddy:
    image: REDACTED
    env_file: envs/${CLUSTER_ENV}/ingress-caddy.env
    networks:
      - clusternet
    ports:
      - target: 80
        published: 80
        protocol: tcp
        mode: host
      - target: 443
        published: 443
        protocol: tcp
        mode: host
    deploy:
      mode: replicated
      replicas: 1
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - node.labels.node_type == ${docker_label_ingress}

d. My complete Caddy config:

{$API_DOMAINS} {
	handle_path /api/* {
		reverse_proxy http://api-1:9001 {
			header_up Host {http.request.host}
		}
	}

	handle /* {
		reverse_proxy http://api-2 {
			header_up Host {http.request.host}
		}
	}
	handle /taxis/ws {
		reverse_proxy http://api-2 {
			header_up Connection Upgrade
			header_up Upgrade websocket
		}
	}
}

{$ADMIN_DOMAIN} {
	handle_path /api/* {
		reverse_proxy http://api-2 {
			header_up Host {http.request.host}
			transport http {
				response_header_timeout 20s
			}
		}
	}

	handle /* {
		reverse_proxy http://admin-dashboard:5000
	}
}

5. Links to relevant resources:

I believe HTTP/1.1 requires a Host header. You might want to make sure you’re always sending that header to the upstream:

reverse_proxy http://backend {
    header_up Host “hardcoded-value”
    transport http {
        versions 1.1
    }
}

If there’s no Host header, Caddy probably can’t establish HTTP/1.1. Just a guess, though.

Not sure if this will help, but it’s worth a shot. Typing on my phone right now, so I can’t test it, sorry!

Edit: just to clarify, if you’re talking HTTP/1.0 to Caddy, I don’t think you can use:

header_up Host {http.request.host}

because there’s no such header in HTTP/1.0. That’s why I’m suggesting to use a hardcoded value to make sure it’s always present.

We’re using HTTP/2, so that should not be a problem. Adding the header_up line does not change anything, unfortunately.

The connection with upstream in reverse_proxy is always http/1.1 except when it’s HTTPS, which it isn’t. The HTTP 101 is an expected response with WS requests.

A client sends a GET HTTP request with an Upgrade header which must also be listed in the Connection header. The server agrees to switch protocols, returning a 101 response meaning the connection has switched from HTTP to WebSocket

2 Likes