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
I was trying to enforce HTTP/1.0 and HTTP/1.1 while writing a publication on Cowboy. The documentation is exposing the
protocolsoption fromcowboy_opts()type where the value is a list containinghttp,http2or both atoms.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.
R27and Cowboy2.10.0on OpenBSDR28and Cowboy2.15.0on DebianHere the code to reproduce the bug (only
protocolsoption is modified during the next sections).Question: is it the right way to enforce HTTP/1.1 or HTTP/2 usage?
Enforcing HTTP1.1
protocolsoption is set to[http].Behaviors expected:
Behaviors encounter:
Enforcing HTTP/2
protocolsoption is set to[http2].Behaviors expected:
Behaviors encounter: