rust-web-server 17.48.0

An HTTP web framework, reverse proxy, and server for Rust supporting HTTP/1.1, HTTP/2, and HTTP/3. Config-driven proxy mode (rws.config.toml with [[route]] / [[upstream]]) or library crate. No third-party HTTP dependencies.
Documentation
---
title: Virtual Hosting
description: Serve multiple domains from a single server instance, each with its own TLS certificate, using SNI-based routing.
---

## Overview

A single `rust-web-server` instance can host multiple domains simultaneously.
At the TLS handshake, the client sends its target hostname via SNI (Server Name
Indication). `SniCertResolver` reads that hostname and selects the matching
certificate before any HTTP traffic is exchanged. The negotiated hostname is
then available in every request handler as `ConnectionInfo::sni_hostname`.

Virtual hosting requires the `http2` or `http3` (default) feature for TLS
support.

## Configuring virtual hosts in `rws.config.toml`

Add one `[[virtual_host]]` block per domain. The top-level `tls_cert_file` /
`tls_key_file` pair is used as a fallback when no SNI hostname matches (or when
the client sends no SNI):

```toml
# Default certificate — used when no virtual host matches
tls_cert_file = "/etc/ssl/default.pem"
tls_key_file  = "/etc/ssl/default.key"

[[virtual_host]]
domain    = "example.com"
cert_file = "/etc/ssl/example.pem"
key_file  = "/etc/ssl/example.key"

[[virtual_host]]
domain    = "api.example.com"
cert_file = "/etc/ssl/api-example.pem"
key_file  = "/etc/ssl/api-example.key"
```

## Configuring virtual hosts via environment variables

The same configuration is available through numbered environment variables,
which is convenient in container environments:

```bash
RWS_CONFIG_VIRTUAL_HOST_0_DOMAIN=example.com
RWS_CONFIG_VIRTUAL_HOST_0_CERT_FILE=/etc/ssl/example.pem
RWS_CONFIG_VIRTUAL_HOST_0_KEY_FILE=/etc/ssl/example.key

RWS_CONFIG_VIRTUAL_HOST_1_DOMAIN=api.example.com
RWS_CONFIG_VIRTUAL_HOST_1_CERT_FILE=/etc/ssl/api-example.pem
RWS_CONFIG_VIRTUAL_HOST_1_KEY_FILE=/etc/ssl/api-example.key
```

## How SNI resolution works

`SniCertResolver` implements `rustls::server::ResolvesServerCert`. It holds a
`HashMap<String, Arc<CertifiedKey>>` keyed by the exact SNI hostname, plus an
optional default. The resolver is built once at startup (or after SIGHUP) by
`create_tls_acceptor_from_vhosts()`:

```rust
// src/tls/mod.rs (simplified)
pub fn create_tls_acceptor_from_vhosts(
    vhosts: &[VirtualHostConfig],
    default_cert: &str,
    default_key: &str,
) -> Result<TlsAcceptor, String>
```

The same function is used for both HTTP/2 (TCP/TLS) and HTTP/3 (QUIC) listeners,
so virtual hosting works transparently across all protocols.

## Reading the SNI hostname in handlers

After the TLS handshake, `ConnectionInfo::sni_hostname` carries the negotiated
hostname as `Option<String>`. For plain HTTP/1.1 connections (no TLS) this
field is `None` and the `Host` header should be used instead.

```rust
fn process(&self, request: &Request, response: Response, connection: &ConnectionInfo) -> Response {
    match &connection.sni_hostname {
        Some(host) => println!("Serving request for: {}", host),
        None       => println!("No SNI — plain HTTP or no matching vhost"),
    }
    response
}
```

## Host-restricted routing with `Router`

Call `.with_host("hostname")` before registering routes to restrict a `Router`
to requests whose SNI hostname (TLS) or `Host` header (plain HTTP) matches:

```rust
use rust_web_server::router::Router;

let mut api_router = Router::new();
api_router.with_host("api.example.com")
    .get("/v1/users", |_req, _params, _conn| { /* ... */ });

let mut www_router = Router::new();
www_router.with_host("example.com")
    .get("/", |_req, _params, _conn| { /* ... */ });
```

## Hot reload

Send `SIGHUP` (or `POST /admin/config/reload`) to hot-reload all virtual host
certificates from disk without restarting the server:

```bash
kill -HUP $(pidof rws)
```

`Server::run_tls` rebuilds `TlsAcceptor` with updated certificates for all
virtual hosts on every SIGHUP. New connections immediately use the refreshed
certificates; existing connections are not interrupted.