Skip to main content

acme/
acme.rs

1//! Run with `ACME_DOMAINS=example.com cargo run --all-features --example acme`.
2//!
3//! Obtains and renews certificates automatically with Let's Encrypt.
4//!
5//! TLS-ALPN-01 requires the CA to reach this server on port 443, so the port
6//! cannot be changed on the public side. The staging directory is used by
7//! default to avoid production rate limits; set `ACME_PRODUCTION=1` for real
8//! certificates.
9//!
10//! Environment:
11//!
12//! - `ACME_DOMAINS`: comma-separated domains, required.
13//! - `ACME_CONTACT`: contact email, optional.
14//! - `ACME_CACHE`: certificate cache directory, defaults to `./acme-cache`.
15//! - `ACME_PORT`: port to bind, defaults to `443`.
16//! - `ACME_PRODUCTION`: set to `1` to use the production directory.
17
18use axum::{routing::get, Router};
19use axum_server::acme::{caches::DirCache, Acceptor, AcmeConfig};
20use futures_util::StreamExt;
21use std::{env, net::SocketAddr};
22
23#[tokio::main]
24async fn main() {
25    let domains = env::var("ACME_DOMAINS")
26        .expect("set ACME_DOMAINS to a comma-separated list of domains")
27        .split(',')
28        .map(str::to_owned)
29        .collect::<Vec<_>>();
30
31    let cache = env::var("ACME_CACHE").unwrap_or_else(|_| "./acme-cache".to_owned());
32    let port = env::var("ACME_PORT")
33        .ok()
34        .and_then(|port| port.parse().ok())
35        .unwrap_or(443);
36    let production = env::var("ACME_PRODUCTION").is_ok_and(|value| value == "1");
37
38    let mut config = AcmeConfig::new(domains)
39        .cache(DirCache::new(cache))
40        .directory_lets_encrypt(production);
41
42    if let Ok(contact) = env::var("ACME_CONTACT") {
43        config = config.contact([format!("mailto:{contact}")]);
44    }
45
46    let mut state = config.state();
47    let acceptor = Acceptor::new(&state);
48
49    // Polling the state is what acquires and renews the certificates.
50    tokio::spawn(async move {
51        while let Some(event) = state.next().await {
52            match event {
53                Ok(event) => println!("acme: {event:?}"),
54                Err(error) => eprintln!("acme error: {error:?}"),
55            }
56        }
57    });
58
59    let app = Router::new().route("/", get(|| async { "Hello, world!" }));
60    let addr = SocketAddr::from(([0, 0, 0, 0], port));
61
62    println!("listening on https://{addr}");
63    if !production {
64        println!("using the Let's Encrypt staging directory");
65    }
66
67    axum_server::bind(addr)
68        .acceptor(acceptor)
69        .serve(app.into_make_service())
70        .await
71        .unwrap();
72}