Serving static html file with Cowboy on release

I’ve been attempting to serve a simple index.html file using cowboy (and failing).
After 12 hours of searching around, and seeing many examples online of similar content being served by cowboy, I’ve come up empty on my own personal project.

I have been successful in serving json and html within the handler, just not a static html file served from /priv in my project directory unfortunately.

Please let me know if there’s anything wrong with what I currently have (routes, listener, and handler) or if you have any suggestions.

Thank you.


% self_app.erl
start(_Type, _Args) ->
	Dispatch = cowboy_router:compile([
		{'_', [
			{"/", cowboy_static, 
					{priv_file, self_handler,"index.html",
						[{mimetypes, {<<"text">>, <<"html">>, []}}]}

	{ok, _} = cowboy:start_clear(self_http_server, [{port, 8080}], #{env =>
		#{dispatch => Dispatch}}),


% self_handler.erl
init(Req0, State) ->
	Req = cowboy_req:reply(200, 
		#{<<"content-type">> => <<"text/html">>}, Req0
	{ok, Req, State}.

System Info

Distro: Linux Mint 20.3 Una base: Ubuntu 20.04 focal
Erlang/OTP 25 [erts-13.0] [source] [64-bit] [smp:20:20] [ds:20:20:10] [async-threads:1] [jit:ns]
Cowboy 2.9.0
1 Like

If you only want to serve a file you don’t need a handler at all, and cowboy_static does all for you.

the second parameter in the tuple {priv_file, self_handler is the application name and not a handler. So if your application is called my_app that would be {priv_file, my_app,"index.html",.

(like the first example in the user guide: Nine Nines: Static files)

You can safely delete the self_handler. Does this help?


Thank you for replying!

I have made changes to cowboy_router:compile to reflect the removal of the handler.
I am still getting a 404 error unfortunately.

Here is my updated route path:

start(_Type, _Args) ->
	Dispatch = cowboy_router:compile([
		{'_', [
			{"/", cowboy_static, {priv_file, self_app,"index.html"}}

	{ok, _} = cowboy:start_clear(self_http_server, [{port, 8080}], #{env =>
		#{dispatch => Dispatch}}),

After I made the change, I exited the release repl console and re-ran make run and everything compiled successfully.

Next, I made my way towards http://localhost:8080/index.html in my browser and I’m presented with the same 404 error as in previous attempts.

This has honestly been a major head-scratcher as it seems like a trivial thing to do. I was able to get a JSON request/response on my first try, however.

1 Like

:thinking: are you sure that your application is called self_app and not self? Do you get error-messages? For example while trying it out I more or less followed the steps in the cowboy-user guide (Nine Nines: Getting started) but used static as a directory name. Later on I made the mistake to use static_app (and not static which is the application name) as the app-name in the route. And got the following errors:

(static@> =CRASH REPORT==== 14-Jun-2022::21:35:18.289700 ===
    initial call: cowboy_stream_h:request_process/3
    pid: <0.499.0>
    registered_name: []
    exception error: bad argument: "Can't resolve the priv_dir of application static_app"
      in function  cowboy_static:priv_path/2 (src/cowboy_static.erl, line 72)
      in call from cowboy_static:init_opts/2 (src/cowboy_static.erl, line 59)
      in call from cowboy_handler:execute/2 (src/cowboy_handler.erl, line 37)
      in call from cowboy_stream_h:execute/3 (src/cowboy_stream_h.erl, line 306)
      in call from cowboy_stream_h:request_process/3 (src/cowboy_stream_h.erl, line 295)
    ancestors: [<0.498.0>,<0.468.0>,<0.467.0>,ranch_sup,<0.456.0>]
    message_queue_len: 0
    messages: []
    links: [<0.498.0>]
    dictionary: []
    trap_exit: false
    status: running
    heap_size: 610
    stack_size: 28
    reductions: 263


Hoorah! I believe passing in self_app was causing an issue. Thank you for catching that!
I may have made the mistake of mixing up the application name and the module name itself (which was self_app.erl).

I setup a clean project to test again and was greeted by Hello World! in the browser.


start(_Type, _Args) ->
	Dispatch = cowboy_router:compile([
		{'_', [{"/assets/[...]", cowboy_static, {priv_dir, example,

	{ok, _} = cowboy:start_clear(my_http_listener, [{port, 8080}], #{env =>
		#{dispatch => Dispatch}}),

I still need to play around with this as I was getting mixed results trying to serve a single file.
In this example I’m serving all files from a directory.
Thank you for your responses, I greatly appreciate it :slight_smile: