Hi, I’m Erika Rowland (a.k.a. erikareads). Hi, I’m Erika. I’m an Ops-shaped Software Engineer, Toolmaker, and Resilience Engineering fan. I like Elixir and Gleam, Reading, and Design. She/Her. Constellation Webring Published on

Serve a Static Directory with Erlang

For a long time, I’ve been serving static files to locally test this website using python3 -m http.server.Actually, I used a custom script that overrode the Handler class to set the mimetype for files without an extension to "text/html", since I use that to get clean urls without .html on the end. I’ve wanted to do this with Erlang for a long time, after I found out that Erlang has a built in httpd server.

Recently, I found outThank you to someone on the Gleam discord who pointed this out to me! that a short command like the python one will be added in OTP 27Investigating the origin of the pull request, I found this list of “serve this directory of static files” commands across a variety of languages. If Erlang isn’t your jam, enjoy!. Soon you will be able to invoke erl -S httpd to serve static files locally. Looking at the diff I was able to write an Elixir script that serves a static directory for me now.

The Script

The core of the script is powered by :httpd.start_service/1 which takes a proplist of settings that configure the httpd server. I always found the list of configuration options intimidating, so I was glad to have a place to start with from the diff.

Required Property

Four properties are required:

  {:port, 8008},
  {:server_name, 'localhost'},
  {:server_root, absolute_path},
  {:document_root, absolute_path}

A few notes:

  • All string properties need to be single-quoted Elixir charlists, because :httpd is expecting Erlang strings, not Elixir binaries.From the Elixir documentation: “The rationale behind this behaviour is to better support Erlang libraries which may return text as charlists instead of Elixir strings.”
  • I had to set :server_name to exactly 'localhost', in order to get my websites relative links to work correctly. So far I can tell, I can run multiple versions of :httpd on different ports with the same name without issue.
  • absolute_path, like the diff, I used an absolute_path path generated using some Erlang functions instead of a local path, but I believe a relative path would work.
  • :server_root is the root directory that all other properties are relative to.
  • :document_root is what I actually want to serve my static files.

Additional Properties

I also set some additional properties for my needs:


The default value is set to :any, I specifically set mine to {127, 0, 0, 1}. I think this would be the same as the :any behavior, but better safe than sorry.


Like I did with Python, I was able to set the mimetype for no extension to text/html by adding {'', 'text/html'} to my list of mimetypes, which I otherwise took from the diff.


This undocumentedThe documentation says to use mime_type, but I found that when I set this to 'text/html', my extensionless files still were rendered as plain text. After investigation, I found that mime_type does nothing and hasn’t since at least OTP-17.0.The default_type property does do what mime_type claims, but isn’t mentioned in the documentation. property sets the default mimetype for files that don’t match the existing :mime_types.


Idiosyncratically, I use home instead of index.html for my folder indexes on my website, Erlang easily let me set this with {:directory_index, ['home']}.I don’t remember where I picked this up from, but I’ve used in my last 3 static site generators.


:httpd comes with a number of modulesThere’s a mod_trace module that enables support for the TRACE request type. I didn’t know these existed, but apparently they’re included in HTTP/1.1. It’s even listed on MDN!? to extend its behavior. For my script, I enabled the same three as the diff:

  • :mod_alias, I believe this enables the directory index behavior.
  • :mod_dir, will generate an Apache style file listing, if a directory index isn’t found, though this shouldn’t apply for my website.
  • :mod_get, which enables GET requests for regular files, which is what I want to a static directory being served.

Other Details


The :inets service must be started before :httpd can work. So my script includes a call to :inets.start() before :httpd.start_service/1.


The :inets Getting Started suggests that after starting :inets, you can start an :httpd server by calling :inets.start(:httpd, config). This does start a httpd server, but in my script it immediately ended because the main process no longer had any work.

I can avoid this behavior by passing --no-halt when calling my script, but I chose to mimic the diff, by setting up a receive do which blocks until it receives a message that the server is down. This gives the same effect, without needing to pass additional arguments on invocation.


I’m glad to hear that Erlang is soon going to get the ability to quickly and easily host a static directory like Python. If you’re interested in this ability now, I’ve put my script here.If you would prefer the escript, I’ve created that here. I prefer the Elixir syntax, but the escript version has the benefit of only needing Erlang to run.

Constellation Webring