Cameleeringsimoom

How do I serve HTTPS with simoom?

Turn on the tls feature, load your certificate into a ServerConfig, and call listen_tls and tls; simoom does TLS 1.3 itself, with no OpenSSL.

simoom has its own TLS 1.3 implementation, with its own cryptography, in the simoom-tls crate. There is nothing to install and nothing to link. This page covers certificates, several names on one server, rotation without restart, and client certificates.

Turn it on

toml
simoom = { version = "0.1", features = ["tls"] }   # or "full" for TLS and HTTP/3

tls supports X25519 and P-256 key exchange, ECDSA P-256 and RSA certificates, and AES-GCM. tls-extra adds P-384 and Ed25519 certificates and ChaCha20-Poly1305.

A self-signed certificate for local testing

simoom::tls::certgen makes certificates for tests:

rust
use simoom::tls::{certgen, ServerConfig, CertifiedKey};
use simoom::{Reply, Request, Server};

fn main() {
    let cert = certgen::self_signed("localhost");
    let key = CertifiedKey::from_pem(cert.cert_pem.as_bytes(), cert.key_pem.as_bytes()).expect("certificate and key");
    let server = Server::builder()
        .listen_tls("127.0.0.1:8443")
        .tls(ServerConfig::new(key))
        .service(|_: &mut Request<'_>, r: Reply<'_>| r.body_static(b"Hello over TLS"))
        .start()
        .unwrap();
    server.join();
}

curl -k https://localhost:8443/ connects (-k because the certificate is self-signed). The localhost certificate also covers 127.0.0.1 and ::1.

Your own certificate

Load a PEM chain and key, for example from Let's Encrypt. Keys in PKCS#8, SEC1 or PKCS#1 form work. Plain and TLS listeners can live on one server:

rust
use simoom::tls::ServerConfig;
use simoom::{Reply, Request, Server};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cert = std::fs::read("/etc/simoom/fullchain.pem")?;
    let key = std::fs::read("/etc/simoom/privkey.pem")?;
    let tls = ServerConfig::from_pem(&cert, &key).map_err(|e| format!("{e:?}"))?;

    let server = Server::builder()
        .listen("0.0.0.0:80") // plain HTTP on one port
        .listen_tls("0.0.0.0:443") // TLS on another
        .tls(tls)
        .service(|req: &mut Request<'_>, r: Reply<'_>| r.body(format!("secure: {}", req.is_secure())))
        .start()?;
    server.join();
    Ok(())
}

On a TLS listener, client and server agree on HTTP/2 or HTTP/1.1 during the handshake (ALPN). Builder::tls sets the ALPN list for you. For HTTP/3, see HTTP/2 and HTTP/3.

Several names on one server

add_sni_cert adds a certificate that is chosen when the client asks for one of its names (SNI), wildcards included. Clients asking for other names get the default certificate:

rust
use simoom::tls::{CertifiedKey, ServerConfig};

fn config(a: CertifiedKey, b: CertifiedKey) -> ServerConfig {
    let mut tls = ServerConfig::new(a); // the default certificate
    tls.add_sni_cert(b); // chosen when the client asks for one of its names
    tls
}

Set tls.strict_sni = true to refuse clients whose name matches no certificate.

Rotating certificates without a restart

ServerHandle::set_tls swaps the configuration. New connections use it; open ones keep theirs:

rust
use simoom::tls::ServerConfig;
use simoom::ServerHandle;

fn reload(server: &ServerHandle) -> Result<(), Box<dyn std::error::Error>> {
    let cert = std::fs::read("/etc/simoom/fullchain.pem")?;
    let key = std::fs::read("/etc/simoom/privkey.pem")?;
    let tls = ServerConfig::from_pem(&cert, &key).map_err(|e| format!("{e:?}"))?;
    server.set_tls(tls)?; // new connections use it; open ones keep theirs
    Ok(())
}

set_quic_tls does the same for HTTP/3. Session tickets issued before the swap resume only if the new configuration shares the old one's ticketer.

Client certificates (mutual TLS)

Require or request client certificates with ServerConfig::client_auth. The ClientAuth and RootStore types live in simoom-tls, which simoom doesn't re-export in 0.1.0, so add it next to simoom (cargo add simoom-tls, same version):

rust
use simoom::tls::ServerConfig;
use simoom::{Outcome, Reply, Request};
use simoom_tls::x509::RootStore;
use simoom_tls::ClientAuth;

fn config(mut tls: ServerConfig) -> Result<ServerConfig, String> {
    let mut roots = RootStore::empty();
    roots.add_pem_file("/etc/simoom/client-ca.pem")?;
    tls.client_auth = ClientAuth::Required(roots);
    Ok(tls)
}

fn whoami(req: &mut Request<'_>, reply: Reply<'_>) -> Outcome {
    match req.tls() {
        Some(info) if !info.peer_certificates().is_empty() => {
            reply.body(format!("client certificate of {} bytes, {}", info.peer_certificates()[0].len(), info.cipher_suite_name()))
        }
        _ => reply.status(401).empty(),
    }
}

ClientAuth::Optional(roots) asks for a certificate but accepts clients without one; the ones that send one are still verified. req.tls() also gives the protocol version, cipher suite, the SNI name and the negotiated ALPN protocol.

What simoom's TLS does and doesn't do

  • TLS 1.3 only on simoom servers. TLS 1.2 exists in simoom-tls behind its tls12 feature, but simoom's features don't turn it on. Clients that only speak TLS 1.2 fail the handshake.
  • 0-RTT is never accepted, by design: early data can be replayed.
  • Session resumption with tickets is on by default, with rotating keys.
  • Constant-time crypto: hardware AES-GCM where the CPU has it, a bitsliced fallback elsewhere, constant-time comparison of secrets.
  • The handshake deadline is Limits::head_timeout (10 s by default).
  • Not independently audited yet. The TLS stack passed testssl.sh and interop with OpenSSL, curl, Python, GnuTLS and Schannel (SECURITY.md), but no outside firm has audited it. If your rules require an audited stack, terminate TLS in front of simoom.

Common questions

Do I need OpenSSL, a C compiler or cmake?

No. simoom's TLS and cryptography are Rust code in the repository, so cross-compiling for another target needs only the Rust target.

Can I use rustls or OpenSSL instead?

Not inside simoom's listeners. You can put a TLS terminator (nginx, haproxy, a cloud load balancer) in front of a plain simoom listener; HTTP/2 with prior knowledge (h2c) is a good protocol between them.

How do I redirect HTTP to HTTPS?

Listen on both ports and answer plain requests with a redirect: if !req.is_secure() { return r.status(308).header("location", url).empty(); }.

Does TLS make start-up slower?

A little. In benchmark campaign 4 (2026-11-12), the full server, with TLS 1.3 and HTTP/3, gave its first answer 1.49 ms after start, against 0.94 ms for the default build without them; hyper, without TLS, took 1.48 ms on the same droplet. Part of the difference is HTTP/3, which starts every event loop at once (benchmarks).

Edit this page on GitHub