Skip to content

Using only HTTP/1.1 or HTTP/2 (not both) #1716

@niamtokik

Description

@niamtokik

I was trying to enforce HTTP/1.0 and HTTP/1.1 while writing a publication on Cowboy. The documentation is exposing the protocols option from cowboy_opts() type where the value is a list containing http, http2 or both atoms.

protocols ([http2, http])

Protocols that may be used when the client connects over cleartext TCP. The default is to allow both HTTP/1.1 and HTTP/2. HTTP/1.1 and HTTP/2 can be disabled entirely by omitting them from the list.

Based on the documentation, I thought it was to select only a subset of the protocols supported by cowboy, but for some reasons, it does not work as expected.

  • tested with Erlang R27 and Cowboy 2.10.0 on OpenBSD
  • tested with Erlang R28 and Cowboy 2.15.0 on Debian

Here the code to reproduce the bug (only protocols option is modified during the next sections).

% src/fu_app.erl
-module(fu_app).
-behaviour(application).
-export([start/2, stop/1]).

start(_StartType, _StartArgs) ->
  start_cowboy(),
  fu_sup:start_link().

stop(_State) ->
  ok.

routes() ->
  [
    {'_', [{"/", http1_handler, #{}}]}
  ].

dispatch() ->
  cowboy_router:compile(routes()).

start_cowboy() ->
  Opts = [{port, 8080}],
  ProtoOpts = #{
    env => #{
      dispatch => dispatch()
    },
    protocols => [http]
  },
  cowboy:start_clear(http1_listener, Opts, ProtoOpts).
% src/http1_handler.erl
-module(http1_handler).
-export([init/2]).

init(Req, State) ->
  NewReq = cowboy_req:reply(200, #{}, <<"ok\n">>, Req),
  {ok, NewReq, State}.

Question: is it the right way to enforce HTTP/1.1 or HTTP/2 usage?

Enforcing HTTP1.1

protocols option is set to [http].

% ...
  ProtoOpts = #{
    env => #{
      dispatch => dispatch()
    },
    protocols => [http]
  },
% ...

Behaviors expected:

  • success when using HTTP/1.0
  • success when using HTTP/1.1
  • fail when using HTTP/2

Behaviors encounter:

  • success when using HTTP/1.0
  • success when using HTTP/1.1
  • success when using HTTP/2
$ curl -v --http1.0 127.0.0.1:8080  
*   Trying 127.0.0.1:8080...
* Established connection to 127.0.0.1 (127.0.0.1 port 8080) from 127.0.0.1 port 33904 
* using HTTP/1.x
> GET / HTTP/1.0
> Host: 127.0.0.1:8080
> User-Agent: curl/8.19.0
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< connection: close
< content-length: 3
< date: Tue, 19 May 2026 18:30:15 GMT
< server: Cowboy
< 
ok
* shutting down connection #1 

$ curl -v --http1.1 127.0.0.1:8080  
*   Trying 127.0.0.1:8080...
* Established connection to 127.0.0.1 (127.0.0.1 port 8080) from 127.0.0.1 port 39372 
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.19.0
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< content-length: 3
< date: Tue, 19 May 2026 18:30:39 GMT
< server: Cowboy
< 
ok
* Connection #0 to host 127.0.0.1:8080 left intact

$ curl -v --http2 127.0.0.1:8080    
*   Trying 127.0.0.1:8080...
* Established connection to 127.0.0.1 (127.0.0.1 port 8080) from 127.0.0.1 port 9810 
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.19.0
> Accept: */*
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAAQAAAAIAAAAA
> Connection: Upgrade, HTTP2-Settings
> 
* Request completely sent off
< HTTP/1.1 101 Switching Protocols
< connection: Upgrade
< upgrade: h2c
< 
* Received 101, Switching to HTTP/2
< HTTP/2 200 
< content-length: 3
< date: Tue, 19 May 2026 18:30:55 GMT
< server: Cowboy
< 
ok
* Connection #0 to host 127.0.0.1:8080 left intact

Enforcing HTTP/2

protocols option is set to [http2].

% ...
  ProtoOpts = #{
    env => #{
      dispatch => dispatch()
    },
    protocols => [http2]
  },
% ...

Behaviors expected:

  • fail when using HTTP/1.0
  • fail when using HTTP/1.1
  • success when using HTTP/2

Behaviors encounter:

  • fail when using HTTP/1.0
  • fail when using HTTP/1.1
  • fail when using HTTP/2
$ curl -v --http1.0 127.0.0.1:8080 
*   Trying 127.0.0.1:8080...
* Established connection to 127.0.0.1 (127.0.0.1 port 8080) from 127.0.0.1 port 32188 
* using HTTP/1.x
> GET / HTTP/1.0
> Host: 127.0.0.1:8080
> User-Agent: curl/8.19.0
> Accept: */*
> 
* Request completely sent off
* Received HTTP/0.9 when not allowed
< closing connection #0
curl: (1) Received HTTP/0.9 when not allowed

$ curl -v --http1.1 127.0.0.1:8080  
*   Trying 127.0.0.1:8080...
* Established connection to 127.0.0.1 (127.0.0.1 port 8080) from 127.0.0.1 port 25820 
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.19.0
> Accept: */*
> 
* Request completely sent off
* Received HTTP/0.9 when not allowed
< closing connection #0
curl: (1) Received HTTP/0.9 when not allowed

$ curl -v --http2 127.0.0.1:8080    
*   Trying 127.0.0.1:8080...
* Established connection to 127.0.0.1 (127.0.0.1 port 8080) from 127.0.0.1 port 19642 
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.19.0
> Accept: */*
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAAQAAAAIAAAAA
> Connection: Upgrade, HTTP2-Settings
> 
* Request completely sent off
* Received HTTP/0.9 when not allowed
< closing connection #0
curl: (1) Received HTTP/0.9 when not allowed

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions