Chapter 8 / 51
Volume 2 · Configuration & Assembly

xs.json in depth · host level & vhosts

Multiple sites in one server: host_default catches everything unmatched, hosts[] splits traffic precisely by domain. This chapter explains the three routing sources and per-host certificate dispatch.

Overview

Positioning: the host is the vhost carrier, the certificate dispatch unit and the script runtime unit. Once this chapter clicks, chapter 20's vhost practice is just a scaling exercise.

8.1 Intro

Many sites on one machine is the server norm: the main site, the API, an admin backend and several customer sites sharing one process. XServer's model is two levels: server (endpoint) × host (site): one server listens on the port, and each request is routed to a host by "which site is it for"; every host has its own static root, script and certificates.

port 80/443 (server) │ split by domain ┌──────────┼──────────┬─────────────┐ ▼ ▼ ▼ ▼ host A host B host C host_default a.com b.com c;d.com (fallback, always present) wwwroot-a wwwroot-b script-c catch-all site cert-a cert-b cert-c

8.2 Host-level fields

FieldConstraintDescription
nameunique within the server, non-emptyhost name; duplicates / empty fail fast
hostsemicolon-separated domainsrequired and non-empty for http/ws hosts with vhosts enabled; e.g. "a.com;www.a.com"
pathdirectory must existstatic root; missing fails fast
devlang / devfile—c / static / custom; with static, devfile may be empty
tls_ca / tls_cert / tls_keyPEM pathshost-level certs → the SNI table's data source (see 8.4)

A domain binds to exactly one host (double binding fails fast); one host may bind several domains (semicolon-separated; matching follows domain case rules).

8.3 The three routing sources

The "target host" of an HTTP request can appear in three places; XServer consults them in order:

SourceShapeTypical scenario
Host headerHost: a.com:8080the vast majority of everyday requests (the port suffix is stripped)
absolute-formGET http://a.com/x HTTP/1.1proxy-style clients
authority-formCONNECT a.com:443tunnel requests
  • HTTP/1.1 mandates Host: missing or duplicated is a protocol error (400) — behavioral correctness, not an optional check
  • Matching no hosts[] entry → fall back to host_default; a keep-alive connection switching Host per request still routes correctly per request
  • On a TLS endpoint, identity selection happens in the handshake (SNI, next section); HTTP-layer routing happens after decryption — the two layers agree

8.4 Host-level certificates & SNI

Every http/ws host may carry its own tls_ca/tls_cert/tls_key. At assembly they enter the SNI table; during the TLS handshake the client names its target domain in the ClientHello (SNI), and the server picks the matching certificate — domain-to-certificate matching completes in the handshake, before any HTTP request begins. The server-side cookie binds the ClientHello's identity selection and the Accept to the same generation, eliminating cross-generation handshake races.

Certificates are hot-swappable: on a reload where the config-level comparison is unchanged (endpoints/structure identical), the certificate hot-swap path applies — an atomic table swap under lock; old identities stay protected by references until in-flight handshakes finish. Certificate formats and pairing rules are in chapter 12.

8.5 Contract

  • Routing: three Host sources → hosts[] exact match → host_default fallback; HTTP/1.1 without Host is a 400.
  • Certificates: host-level certs dispatch by SNI; a host without certificates cannot complete handshake identity selection on a TLS endpoint.
  • Scripts: every devlang=c host gets an independent script runtime — reloads publish as the complete generation of the host's server.

Common mistakes

Assuming host is "prefix matching"
Symptom
Configured "api.com", expected "www.api.com" to match too — it lands in default.
Cause
host binding is exact matching (each entry of the semicolon list compared whole); no wildcards, no prefixes.
✗ Wrong expectation
"host": "api.com"
/* expecting www.api.com to match too */
✓ Right
"host": "api.com;www.api.com"
hosts[] entry with an empty host field
Symptom
Startup fails: the host field is required and non-empty for http/ws hosts with vhosts enabled.
Cause
A vhost with an empty domain has no routing meaning — "catch everything" is host_default's job, not an empty host.

Exercises

BasicTwo sites on one port

Configure one server with host_default (site A) + hosts[] (site B bound to test.local); put different index.html files in the two wwwroots.

Acceptance: curl -H "Host: test.local" and a request without a Host header each hit their own site.
AdvancedVerify all three sources

Use curl's --resolve and the absolute form (curl -x http://127.0.0.1:8080 http://test.local/, proxy style) to trigger the Host-header and absolute-form routing sources respectively.

Acceptance: both shapes hit the same host; stripping all identity yields 400 (HTTP/1.1 mandates Host).
ChallengeSwitching sites within keep-alive

Write a script that sends two requests with different Host values over one keep-alive connection and verify per-request routing.

Acceptance: each request hits its own site while the connection is reused (same local port).

Summary

PointConclusion
Two-level modelserver = endpoint; host = site (static root / script / certs)
RoutingHost header / absolute-form / authority-form, exact matching
Fallbackhost_default always exists; unmatched traffic goes there
Certificateshost level → SNI table, dispatched at handshake; hot-swappable
API references in this chapter
xrt further reading