Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/moqtail/moqtail/llms.txt

Use this file to discover all available pages before exploring further.

WebTransport is a modern protocol that enables low-latency, bidirectional communication between clients and servers. The MOQtail relay server uses WebTransport over QUIC, which requires proper SSL/TLS certificate configuration.

Understanding WebTransport Requirements

WebTransport has strict security requirements:
  • HTTPS/TLS is mandatory - No plain text connections allowed
  • Valid certificates required - Browsers verify certificate chains
  • UDP protocol - Uses QUIC (UDP-based) instead of TCP
  • Browser support - Currently best supported in Chrome/Chromium
Self-signed certificates require special browser configuration for development. Production deployments should use certificates from trusted Certificate Authorities.

Development Setup

For local development, use mkcert to generate trusted self-signed certificates.

Installing mkcert

1

Install mkcert

brew install mkcert
brew install nss  # if using Firefox
2

Install local Certificate Authority

This creates and installs a local CA in your system trust store:
mkcert -install
You should see output like:
Created a new local CA at "/Users/username/Library/Application Support/mkcert"
The local CA is now installed in the system trust store!
3

Generate certificates for localhost

Navigate to the relay certificate directory:
cd apps/relay/cert
Generate certificates for localhost and common local addresses:
mkcert -key-file key.pem -cert-file cert.pem localhost 127.0.0.1 ::1
This creates two files:
  • cert.pem - Public certificate
  • key.pem - Private key
The default relay configuration expects these files at apps/relay/cert/cert.pem and apps/relay/cert/key.pem.

Custom Domains and IP Addresses

To support additional domains or IP addresses:
# Include custom domain
mkcert -key-file key.pem -cert-file cert.pem localhost 127.0.0.1 ::1 dev.example.com

# Include LAN IP address
mkcert -key-file key.pem -cert-file cert.pem localhost 127.0.0.1 ::1 192.168.1.100

# Include multiple domains and IPs
mkcert -key-file key.pem -cert-file cert.pem \
  localhost 127.0.0.1 ::1 \
  dev.moqtail.local \
  192.168.1.100 \
  10.0.0.50

Browser Configuration

Chrome has the best WebTransport support and requires enabling developer mode for self-signed certificates.
1

Enable WebTransport Developer Mode

  1. Open Chrome and navigate to:
chrome://flags/#webtransport-developer-mode
  1. Find “WebTransport Developer Mode”
  2. Set to “Enabled”
  3. Click “Relaunch” to restart Chrome
2

Verify the setting

After restart, verify WebTransport support:
// In browser console (F12)
'WebTransport' in window
// Should return: true
WebTransport Developer Mode allows connections to servers with certificates signed by untrusted CAs. This is essential for development with mkcert but should never be used in production.

Firefox

Firefox WebTransport support is experimental and not fully tested with MOQtail. Chrome is strongly recommended.
For Firefox (experimental):
  1. Navigate to about:config
  2. Search for network.webtransport.enabled
  3. Set to true
  4. Accept the warning

Edge

Microsoft Edge WebTransport configuration steps are pending. If you successfully configure Edge for MOQtail, please contribute the steps!
Edge uses the same Chromium engine as Chrome, so similar flags should be available at:
edge://flags/#webtransport-developer-mode

Safari

WebTransport is not yet supported in Safari.

Production Setup

For production deployments, use certificates from a trusted Certificate Authority.

Let’s Encrypt with Certbot

1

Install Certbot

sudo apt update
sudo apt install certbot
2

Obtain certificates

# For HTTP-01 challenge (port 80 must be available)
sudo certbot certonly --standalone -d relay.example.com

# Or for DNS-01 challenge (no port 80 needed)
sudo certbot certonly --manual --preferred-challenges dns -d relay.example.com
3

Configure relay with Let's Encrypt certificates

Certificates are typically stored at:
/etc/letsencrypt/live/relay.example.com/fullchain.pem
/etc/letsencrypt/live/relay.example.com/privkey.pem
Run the relay with these certificates:
cargo run --release --bin relay -- \
  --port 443 \
  --host 0.0.0.0 \
  --cert-file /etc/letsencrypt/live/relay.example.com/fullchain.pem \
  --key-file /etc/letsencrypt/live/relay.example.com/privkey.pem
4

Setup auto-renewal

Certbot automatically installs a renewal timer. Verify it:
sudo systemctl status certbot.timer
Test renewal:
sudo certbot renew --dry-run

Certificate Requirements

Your production certificates must:
  1. Be PEM-encoded - MOQtail expects PEM format
  2. Include full chain - Certificate + intermediate certificates
  3. Match the domain - Certificate CN/SAN must match relay domain
  4. Be currently valid - Not expired or not yet valid
  5. Use supported algorithms - RSA 2048+ or ECDSA P-256+

Converting Certificate Formats

If you have certificates in other formats:
# Convert DER certificate to PEM
openssl x509 -inform der -in cert.der -out cert.pem

# Convert DER key to PEM
openssl rsa -inform der -in key.der -out key.pem

Verifying Your Setup

Test Certificate Installation

Verify the relay can load your certificates:
cargo run --bin relay -- \
  --cert-file apps/relay/cert/cert.pem \
  --key-file apps/relay/cert/key.pem
Successful output:
moqtail/0.12.0+a1b2c3d is running at https://localhost:4433

Inspect Certificate Details

# View certificate information
openssl x509 -in apps/relay/cert/cert.pem -text -noout

# Check certificate validity
openssl x509 -in apps/relay/cert/cert.pem -noout -dates

# Verify certificate and key match
openssl x509 -in cert.pem -noout -modulus | openssl md5
openssl rsa -in key.pem -noout -modulus | openssl md5
# These should output the same hash

Test WebTransport Connection

Create a simple test HTML file:
test.html
<!DOCTYPE html>
<html>
<head>
  <title>WebTransport Test</title>
</head>
<body>
  <h1>WebTransport Connection Test</h1>
  <button onclick="testConnection()">Test Connection</button>
  <pre id="output"></pre>

  <script>
    async function testConnection() {
      const output = document.getElementById('output');
      
      try {
        output.textContent = 'Connecting...\n';
        
        const url = 'https://localhost:4433';
        const transport = new WebTransport(url);
        
        await transport.ready;
        output.textContent += 'Connected successfully!\n';
        output.textContent += `Server: ${url}\n`;
        
        await transport.close();
        output.textContent += 'Connection closed.\n';
      } catch (error) {
        output.textContent += `Error: ${error.message}\n`;
        console.error('Connection failed:', error);
      }
    }
  </script>
</body>
</html>
Open in Chrome with WebTransport Developer Mode enabled.

Firewall Configuration

WebTransport uses UDP, not TCP. Ensure your firewall allows UDP traffic:
# Allow UDP on port 4433
sudo ufw allow 4433/udp

# For production (port 443)
sudo ufw allow 443/udp

Cloud Provider Security Groups

AWS Security Group:
Type: Custom UDP
Protocol: UDP
Port Range: 4433
Source: 0.0.0.0/0
GCP Firewall Rule:
gcloud compute firewall-rules create allow-webtransport \
  --allow udp:4433 \
  --source-ranges 0.0.0.0/0 \
  --target-tags webtransport-server
Azure Network Security Group:
Priority: 1000
Name: Allow-WebTransport
Port: 4433
Protocol: UDP
Source: Any
Destination: Any
Action: Allow

Troubleshooting

Symptoms:
Failed to construct 'WebTransport': 
The server's certificate was not trusted.
Solutions:
  1. Verify mkcert CA is installed: mkcert -CAROOT
  2. Enable WebTransport Developer Mode in Chrome
  3. Check certificate includes the domain/IP you’re connecting to
  4. Restart browser after enabling developer mode
Error:
Failed to load identity from PEM files: 
Error reading certificate file
Solutions:
# Verify files exist
ls -la apps/relay/cert/*.pem

# Check file format
openssl x509 -in apps/relay/cert/cert.pem -text -noout
openssl rsa -in apps/relay/cert/key.pem -check

# Regenerate if corrupted
cd apps/relay/cert
mkcert -key-file key.pem -cert-file cert.pem localhost 127.0.0.1 ::1
Possible causes:
  • Certificate doesn’t include remote IP/domain
  • Firewall blocking UDP traffic
  • Server bound to localhost instead of 0.0.0.0
Solutions:
# Regenerate cert with your public IP
mkcert -key-file key.pem -cert-file cert.pem \
  localhost 127.0.0.1 ::1 YOUR.PUBLIC.IP.ADDRESS

# Bind to all interfaces
cargo run --bin relay -- --host 0.0.0.0

# Check firewall
sudo ufw status
sudo ufw allow 4433/udp
Check:
// In browser console
'WebTransport' in window
If returns false:
  • Update to latest Chrome (version 97+)
  • Enable chrome://flags/#webtransport-developer-mode
  • Try Chrome Canary for cutting-edge support
  • Firefox and Safari don’t fully support WebTransport yet

Security Best Practices

Never use WebTransport Developer Mode in production. It disables important security checks.

Development

Do:
  • Use mkcert for local development certificates
  • Enable WebTransport Developer Mode only in development
  • Keep certificates in apps/relay/cert/ (gitignored)
  • Regenerate certificates if you add new domains/IPs
Don’t:
  • Commit private keys to version control
  • Share mkcert CA root certificate
  • Use self-signed certificates in production
  • Disable certificate validation in production code

Production

Do:
  • Use certificates from trusted CAs (Let’s Encrypt, DigiCert, etc.)
  • Implement automatic certificate renewal
  • Monitor certificate expiration dates
  • Use certificate pinning for critical applications
  • Keep private keys secure (mode 600, encrypted storage)
  • Use separate certificates per environment
Don’t:
  • Use self-signed certificates
  • Share certificates across environments
  • Commit production certificates to repositories
  • Use weak key sizes (< 2048 bits RSA)

Code Reference

The relay loads certificates using the wtransport library:
apps/relay/src/server/config.rs:117
pub async fn build_server_config(&self) -> Result<ServerConfig> {
  let identity = match Identity::load_pemfiles(&self.cert_file, &self.key_file).await {
    Ok(identity) => identity,
    Err(e) => {
      error!("Failed to load identity from PEM files: {:?}", e);
      return Err(e.into());
    }
  };

  let config = ServerConfig::builder()
    .with_bind_default(self.port)
    .with_identity(identity)
    .keep_alive_interval(Some(Duration::from_secs(self.keep_alive_interval)))
    .max_idle_timeout(Some(Duration::from_secs(self.max_idle_timeout)))
    .unwrap()
    .build();

  Ok(config)
}

Next Steps

Relay Server Configuration

Configure advanced relay server options

Connect Clients

Connect WebTransport clients to your relay

Build docs developers (and LLMs) love