Modular H2O Server Configuration
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.
"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/
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.
escape: json
format: "{\"at\":%{msec}t,\"ip\":\"%h\",\"method\":\"%m\",\"status\":%s,\"path\":\"%U\"}"
"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/
"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/
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.