# Configure TLS Settings for iroh Endpoints: A Complete Guide to Certificate Verification

> Learn to configure TLS settings for iroh endpoints using CaTlsConfig and rustls ClientConfig. This guide simplifies certificate verification for secure connections.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-07-09

---

**To configure TLS settings for iroh endpoints, use the `CaTlsConfig` type from the `iroh::tls` module to define certificate verification behavior, then convert it to a `rustls::ClientConfig` using the `client_config()` method and pass it to endpoint builders such as `RelayBuilder`.**

The `iroh` crate leverages the [rustls](https://github.com/rustls/rustls) TLS stack for all encrypted connections outside the iroh network, including relay communication, PKARR servers, and DNS-over-HTTPS resolvers. All public TLS APIs are centralized in the **tls** module of the `iroh-relay` crate and re-exported at the top level of the `iroh` crate for convenient access.

## Understanding the iroh TLS Architecture

Iroh delegates all cryptographic operations to `rustls`, using Mozilla's root certificate store via `webpki-roots` as the default trust anchor. The implementation in [`iroh-relay/src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/tls.rs) defines a `CaTlsConfig` struct that stores a `mode` enum and an optional list of extra root certificates. This architecture allows you to configure certificate verification for relay clients, DNS resolvers, and other external endpoints without modifying low-level TLS code.

## Core TLS Configuration Types

### CaTlsConfig

`CaTlsConfig` is the primary configuration struct that controls how clients verify server certificate chains. It supports five verification modes through internal enum variants:

- **EmbeddedWebPki** – Uses the built-in Mozilla root store (`webpki_roots::TLS_SERVER_ROOTS`)
- **System** – Delegates to the OS certificate store when the `platform-verifier` feature is enabled
- **ExtraRootsOnly** – Trusts only explicitly supplied root certificates
- **CustomServerCertVerifier** – Accepts a closure that creates a `rustls::client::ServerCertVerifier`
- **InsecureSkipVerify** – Disables verification entirely for test builds only

### TlsConfig

`TlsConfig` bundles a socket address with certificate configuration. For clients, this wraps a `CaTlsConfig`; for servers, it may contain a `CertConfig::Manual` with a `rustls::ServerConfig`. In [`iroh-relay/src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/tls.rs), the `TlsConfig` struct is attached to `Relay` instances via `relay.tls = Some(tls)`.

### Default Crypto Provider

The `default_provider()` function returns the global `rustls::crypto::CryptoProvider`, selecting either the `ring` or `aws-lc-rs` implementation based on feature flags. This provider is required when building client configurations.

## Configuring Client TLS Verification

### Using Embedded Mozilla Roots

The default configuration trusts Mozilla's root certificates and works for most production scenarios:

```rust
use iroh::tls::{CaTlsConfig, default_provider};

let ca_cfg = CaTlsConfig::embedded();
let client_cfg = ca_cfg.client_config(default_provider())
    .expect("TLS client config creation failed");

```

### Trusting System Certificates

When the `platform-verifier` feature is available, delegate to the OS certificate store:

```rust
let ca_cfg = CaTlsConfig::system();
let client_cfg = ca_cfg.client_config(default_provider()).unwrap();

```

### Supplying Custom Root Certificates

For private CA infrastructure, provide DER-encoded certificates directly:

```rust
use iroh::tls::CaTlsConfig;
use rustls::pki_types::CertificateDer;

// Load your PEM and convert to DER...
let my_root: CertificateDer<'static> = load_my_root_cert();

let ca_cfg = CaTlsConfig::custom_roots(vec![my_root]);
let client_cfg = ca_cfg.client_config(default_provider()).unwrap();

```

When `system()` is unavailable (such as on Android), the `EmbeddedWebPki` mode always loads the Mozilla set and appends any `extra_roots` you supply.

## Applying TLS Config to Endpoints

Pass the `rustls::ClientConfig` to endpoint builders when constructing relay clients:

```rust
use iroh::{tls::{CaTlsConfig, default_provider}, util::RelayBuilder};

let ca_cfg = CaTlsConfig::embedded();
let client_cfg = ca_cfg.client_config(default_provider()).unwrap();

let relay = RelayBuilder::new()
    .tls_config(client_cfg)
    .build();

```

The DNS-over-HTTPS resolver in [`iroh-dns-server/src/http/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns-server/src/http/tls.rs) uses the same pattern, building a `rustls::ClientConfig` via `CaTlsConfig` methods.

## Server TLS Configuration

For server endpoints (such as relay servers), create a `TlsConfig` containing a `CertConfig::Manual` with a `rustls::ServerConfig`. The test utilities in [`iroh/tests/patchbay/util.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/tests/patchbay/util.rs) demonstrate this pattern using `self_signed_tls_certs_and_config()` to generate certificates for testing.

## Testing and Development Modes

For integration tests, use the dangerous configuration that skips verification:

```rust
use iroh::tls::CaTlsConfig;

let ca_cfg = CaTlsConfig::insecure_skip_verify();
let client_cfg = ca_cfg.client_config(default_provider()).unwrap();

```

**⚠️ Critical:** This mode is gated behind `#[cfg(test)]` or the `test-utils` feature flag. The `make_dangerous_client_config()` helper in [`iroh/src/test_utils.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/test_utils.rs) provides a convenient wrapper, but this must never ship in production code.

## Summary

- **CaTlsConfig** controls certificate verification via five modes: embedded Mozilla roots, system trust, custom roots, custom verifiers, or test-only insecure mode.
- **Source files:** [`iroh-relay/src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/tls.rs) contains the implementation; [`iroh/src/tls/mod.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/mod.rs) provides public re-exports.
- **Client configuration:** Call `client_config()` with `default_provider()` to generate a `rustls::ClientConfig`.
- **Endpoint integration:** Pass the configuration to `RelayBuilder::tls_config()` or similar endpoint constructors.
- **Custom roots:** Use `CaTlsConfig::custom_roots()` with DER-encoded certificates for private CA infrastructure.
- **Testing:** Use `insecure_skip_verify()` only behind test gates, not in production.

## Frequently Asked Questions

### How do I configure iroh to trust a corporate self-signed certificate?

Use `CaTlsConfig::custom_roots()` and pass a vector of `CertificateDer` objects representing your corporate root CA. Convert your PEM certificates to DER format, then build the client config with `ca_cfg.client_config(default_provider())`.

### What is the difference between `CaTlsConfig::embedded()` and `CaTlsConfig::system()`?

`embedded()` uses Mozilla's root certificate bundle compiled into the binary via `webpki-roots`, while `system()` delegates verification to the operating system's certificate store when the `platform-verifier` feature is enabled. System verification is preferred on desktop platforms but may be unavailable on embedded or Android targets.

### Where does iroh store TLS configuration for the relay client?

The `Relay` struct stores an optional `TlsConfig` in its `tls` field. When building a relay client, you provide the `rustls::ClientConfig` via `RelayBuilder::tls_config()`, which the relay then uses for all HTTPS connections to the relay server.

### Can I use a custom certificate verifier instead of the standard WebPKI?

Yes. Use `CaTlsConfig::custom_server_cert_verifier()` and supply a closure that creates a `rustls::client::ServerCertVerifier`. This allows you to implement custom pinning logic, certificate transparency checking, or other validation policies beyond the standard root-of-trust model.