Chrono Logo

Modular H2O Server Configuration

author avatar

Toru Maesaka

Founder and Software Engineer

As configuration files grow, so do their complexity and the difficulty in managing them. This can lead to production incidents and steep learning curves for anyone working on them. Modularizing configuration files can simplify configuration management, making them easier and safer to work on. This blog post demonstrates how to manage the H2O server's configuration file in a modular way.

H2O: The Optimized HTTP Server

H2O is a high-performance HTTP server that adheres to the latest standards, often adopting cutting-edge specifications such as HTTP/3. The author is an active participant in the IETF, working to improve internet standards. H2O reflects these efforts, showcasing the latest advancements in HTTP technologies. As the internet-facing server of Fastly, H2O handles a significant portion of global web traffic.

Modular H2O Configuration

From a usage perspective, H2O is very simple software. The server only requires a single YAML configuration file. A simple multi-host configuration file might look like:

hosts:
  "site1.example.com":
    listen:
      port: 443
      ssl:
        certificate-file: /path/to/site1-fullchain.pem
        key-file: /path/to/site1-privkey.pem
    access-log:
      path: /var/log/h2o/site1-access.log
    paths:
      "/":
        file.dir: /srv/site1.example.com/

  "site2.example.com":
    listen:
      port: 443
      ssl:
        certificate-file: /path/to/site2-fullchain.pem
        key-file: /path/to/site2-privkey.pem
    access-log:
      path: /var/log/h2o/site2-access.log
    paths:
      "/":
        proxy.reverse.url: http://downstream-host:port/

While a simple H2O configuration like the above is easy to manage, using H2O's dynamic capabilities such as the reproxy directive and mruby-based scripting can quickly increase the size and complexity of the configuration file. This is where H2O's lesser known configuration file inclusion functionality comes in handy. Here is an example approach that extracts host-specific configurations into their own files.

/etc/h2o/site1.example.com.h2o.conf
"site1.example.com":
  listen:
    port: 443
    ssl:
      certificate-file: /path/to/site1-fullchain.pem
      key-file: /path/to/site1-privkey.pem
  access-log:
    path: /var/log/h2o/site1-access.log
  paths:
    "/":
      file.dir: /srv/site1.example.com/
/etc/h2o/site2.example.com.h2o.conf
"site2.example.com":
  listen:
    port: 443
    ssl:
      certificate-file: /path/to/site2-fullchain.pem
      key-file: /path/to/site2-privkey.pem
  access-log:
    path: /var/log/h2o/site2-access.log
  paths:
    "/":
      proxy.reverse.url: http://downstream-host:port/
/etc/h2o/h2o.conf (The main configuration file)
hosts:
  <<: !file /etc/h2o/site1.example.com.h2o.conf
  <<: !file /etc/h2o/site2.example.com.h2o.conf

error-log: /var/log/h2o/error.log

Effective Configuration Reuse

The example so far was intentionally made similar to virtual host files, but H2O allows the configuration file to be split into more granular pieces. For example, suppose there is a requirement for all hosts to log HTTP requests in a specific JSON format:

"site1.example.com":
  access-log:
    path: /var/log/h2o/site1-access.log
    format: "{\"at\":%{msec}t,\"ip\":\"%h\",\"method\":\"%m\",\"status\":%s,\"path\":\"%U\"}"
    escape: json

The custom log format can be extracted into its own configuration file, which can then be included by host configuration files. Notice that the included file can contain partial configuration, making it useful for mixing with host-specific settings.

/etc/h2o/access-log-format.h2o.conf
escape: json
format: "{\"at\":%{msec}t,\"ip\":\"%h\",\"method\":\"%m\",\"status\":%s,\"path\":\"%U\"}"
/etc/h2o/site1.example.com.h2o.conf
"site1.example.com":
  listen:
    port: 443
    ssl:
      certificate-file: /path/to/site1-fullchain.pem
      key-file: /path/to/site1-privkey.pem
  access-log:
    path: /var/log/h2o/site1-access.log
    <<: !file /etc/h2o/access-log-format.h2o.conf
  paths:
    "/":
      file.dir: /srv/site1.example.com/
/etc/h2o/site2.example.com.h2o.conf
"site2.example.com":
  listen:
    port: 443
    ssl:
      certificate-file: /path/to/site2-fullchain.pem
      key-file: /path/to/site2-privkey.pem
  access-log:
    path: /var/log/h2o/site2-access.log
    <<: !file /etc/h2o/access-log-format.h2o.conf
  paths:
    "/":
      proxy.reverse.url: http://downstream-host:port/
/etc/h2o/h2o.conf
hosts:
  <<: !file /etc/h2o/site1.example.com.h2o.conf
  <<: !file /etc/h2o/site2.example.com.h2o.conf

error-log: /var/log/h2o/error.log

As demonstrated above, H2O's configuration mechanism allows system admins to extract common settings in a dedicated file and include it where needed. Alternatively, a large configuration file can be split into manageable pieces, such as at the host level.

Final Considerations

Structuring the server configuration into smaller pieces can be satisfying, but there is also value in managing the entire configuration in one place. For smaller projects, a subdivided configuration might become cumbersome. It is also important to consider that using a configuration management system can abstract away these software-specific nuances. Regardless of the software, it is crucial to carefully consider what is beneficial for the engineering team.

Did you enjoy the content? Follow us on LinkedIn to help us grow and continue delivering blog posts like this one. Your support is appreciated.