salvo-tls-acme

Configure TLS/HTTPS with automatic certificate management via ACME (Let's Encrypt). Use for production deployments with secure connections.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "salvo-tls-acme" with this command: npx skills add salvo-rs/salvo-skills/salvo-rs-salvo-skills-salvo-tls-acme

Salvo TLS and ACME Configuration

This skill helps configure TLS/HTTPS and automatic certificate management in Salvo applications.

TLS with Rustls

Setup

[dependencies]
salvo = { version = "0.89.0", features = ["rustls"] }

Basic TLS Configuration

use salvo::prelude::*;
use salvo::conn::rustls::{Keycert, RustlsConfig};

#[handler]
async fn hello() -> &'static str {
    "Hello over HTTPS!"
}

#[tokio::main]
async fn main() {
    let router = Router::new().get(hello);

    // Load certificate and private key
    let config = RustlsConfig::new(
        Keycert::new()
            .cert_from_path("certs/cert.pem")
            .unwrap()
            .key_from_path("certs/key.pem")
            .unwrap()
    );

    let acceptor = TcpListener::new("0.0.0.0:443")
        .rustls(config)
        .bind()
        .await;

    Server::new(acceptor).serve(router).await;
}

Certificate from Memory

use salvo::conn::rustls::{Keycert, RustlsConfig};

let cert_pem = include_bytes!("../certs/cert.pem");
let key_pem = include_bytes!("../certs/key.pem");

let config = RustlsConfig::new(
    Keycert::new()
        .cert(cert_pem.to_vec())
        .key(key_pem.to_vec())
);

ACME (Let's Encrypt) Auto-Certificates

Setup

[dependencies]
salvo = { version = "0.89.0", features = ["acme"] }

HTTP-01 Challenge

use salvo::prelude::*;
use salvo::conn::acme::{AcmeConfig, AcmeListener, ChallengeType};

#[handler]
async fn hello() -> &'static str {
    "Hello with auto-certificate!"
}

#[tokio::main]
async fn main() {
    let router = Router::new().get(hello);

    // Configure ACME
    let config = AcmeConfig::builder()
        .domains(["example.com", "www.example.com"])
        .contacts(["mailto:admin@example.com"])
        .challenge_type(ChallengeType::Http01)
        .cache_path("./acme_cache")
        .build()
        .unwrap();

    // ACME listener handles HTTP-01 challenges and serves HTTPS
    let acceptor = AcmeListener::builder()
        .acme_config(config)
        .bind("0.0.0.0:443")
        .await;

    Server::new(acceptor).serve(router).await;
}

TLS-ALPN-01 Challenge

For environments where port 80 is not available:

use salvo::conn::acme::{AcmeConfig, AcmeListener, ChallengeType};

let config = AcmeConfig::builder()
    .domains(["example.com"])
    .contacts(["mailto:admin@example.com"])
    .challenge_type(ChallengeType::TlsAlpn01)
    .cache_path("./acme_cache")
    .build()
    .unwrap();

Force HTTPS Redirect

use salvo::prelude::*;

#[handler]
async fn force_https(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
    // Check if request is HTTP (not HTTPS)
    if req.uri().scheme_str() == Some("http") {
        let host = req.header::<String>("Host").unwrap_or_default();
        let path = req.uri().path_and_query().map(|p| p.as_str()).unwrap_or("/");
        let https_url = format!("https://{}{}", host, path);

        res.status_code(StatusCode::MOVED_PERMANENTLY);
        res.headers_mut().insert("Location", https_url.parse().unwrap());
        ctrl.skip_rest();
        return;
    }

    ctrl.call_next(req, depot, res).await;
}

HTTP and HTTPS on Different Ports

use salvo::prelude::*;
use salvo::conn::rustls::{Keycert, RustlsConfig};

#[tokio::main]
async fn main() {
    let router = Router::new().get(hello);

    // HTTPS on 443
    let tls_config = RustlsConfig::new(
        Keycert::new()
            .cert_from_path("certs/cert.pem").unwrap()
            .key_from_path("certs/key.pem").unwrap()
    );

    let https_acceptor = TcpListener::new("0.0.0.0:443")
        .rustls(tls_config)
        .bind()
        .await;

    // HTTP on 80 (for redirects or ACME challenges)
    let http_acceptor = TcpListener::new("0.0.0.0:80")
        .bind()
        .await;

    // Run both servers
    tokio::join!(
        Server::new(https_acceptor).serve(router.clone()),
        Server::new(http_acceptor).serve(Router::new().hoop(redirect_to_https)),
    );
}

#[handler]
async fn redirect_to_https(req: &mut Request, res: &mut Response) {
    let host = req.header::<String>("Host").unwrap_or_default();
    let path = req.uri().path();
    res.render(salvo::writing::Redirect::permanent(format!("https://{}{}", host, path)));
}

Certificate Hot Reload

use salvo::prelude::*;
use salvo::conn::rustls::{Keycert, RustlsConfig};
use std::sync::Arc;
use tokio::sync::RwLock;

// Reload certificates without restarting
async fn reload_certificates(config: Arc<RwLock<RustlsConfig>>) {
    let new_config = RustlsConfig::new(
        Keycert::new()
            .cert_from_path("certs/cert.pem").unwrap()
            .key_from_path("certs/key.pem").unwrap()
    );

    let mut guard = config.write().await;
    *guard = new_config;
}

HTTP/2 Support

HTTP/2 is automatically enabled when using Rustls:

use salvo::prelude::*;
use salvo::conn::rustls::{Keycert, RustlsConfig};

// HTTP/2 is enabled by default with TLS
let config = RustlsConfig::new(
    Keycert::new()
        .cert_from_path("certs/cert.pem").unwrap()
        .key_from_path("certs/key.pem").unwrap()
);

HTTP/3 (QUIC) Support

[dependencies]
salvo = { version = "0.89.0", features = ["quinn"] }
use salvo::prelude::*;
use salvo::conn::quinn::QuinnListener;

#[tokio::main]
async fn main() {
    let router = Router::new().get(hello);

    let acceptor = QuinnListener::builder()
        .cert_path("certs/cert.pem")
        .key_path("certs/key.pem")
        .bind("0.0.0.0:443")
        .await;

    Server::new(acceptor).serve(router).await;
}

Security Headers for HTTPS

use salvo::prelude::*;

#[handler]
async fn security_headers(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
    // HTTP Strict Transport Security
    res.headers_mut().insert(
        "Strict-Transport-Security",
        "max-age=31536000; includeSubDomains; preload".parse().unwrap()
    );

    // Prevent mixed content
    res.headers_mut().insert(
        "Content-Security-Policy",
        "upgrade-insecure-requests".parse().unwrap()
    );

    ctrl.call_next(req, depot, res).await;
}

Complete ACME Example

use salvo::prelude::*;
use salvo::conn::acme::{AcmeConfig, AcmeListener, ChallengeType};

#[handler]
async fn hello() -> &'static str {
    "Hello with Let's Encrypt!"
}

#[handler]
async fn security_headers(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
    res.headers_mut().insert(
        "Strict-Transport-Security",
        "max-age=31536000; includeSubDomains".parse().unwrap()
    );
    ctrl.call_next(req, depot, res).await;
}

#[tokio::main]
async fn main() {
    let router = Router::new()
        .hoop(security_headers)
        .get(hello);

    let acme_config = AcmeConfig::builder()
        .domains(["example.com", "www.example.com"])
        .contacts(["mailto:admin@example.com"])
        .challenge_type(ChallengeType::Http01)
        .cache_path("./acme_cache")
        .directory_url("https://acme-v02.api.letsencrypt.org/directory")
        .build()
        .unwrap();

    let acceptor = AcmeListener::builder()
        .acme_config(acme_config)
        .bind("0.0.0.0:443")
        .await;

    println!("Server running on https://example.com");
    Server::new(acceptor).serve(router).await;
}

Staging Environment

Use Let's Encrypt staging for testing:

let acme_config = AcmeConfig::builder()
    .domains(["example.com"])
    .contacts(["mailto:admin@example.com"])
    .directory_url("https://acme-staging-v02.api.letsencrypt.org/directory")  // Staging
    .build()
    .unwrap();

Best Practices

  1. Use ACME in production: Automatic certificate renewal
  2. Set HSTS header: Force browsers to use HTTPS
  3. Enable HTTP/2: Better performance with TLS
  4. Test with staging: Use Let's Encrypt staging before production
  5. Cache certificates: Persist to disk for restart recovery
  6. Monitor expiration: Alert before certificates expire
  7. Redirect HTTP to HTTPS: Don't serve content over HTTP
  8. Use strong ciphers: Let Rustls handle cipher selection

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

salvo-concurrency-limiter

No summary provided by upstream source.

Repository SourceNeeds Review
General

salvo-static-files

No summary provided by upstream source.

Repository SourceNeeds Review
General

salvo-openapi

No summary provided by upstream source.

Repository SourceNeeds Review
General

salvo-realtime

No summary provided by upstream source.

Repository SourceNeeds Review