Quick Start Guide¶
This guide walks through obtaining a certificate using ACMEOW.
Prerequisites¶
Before starting, you need:
A domain name you control
Access to create DNS TXT records for your domain
Python 3.10 or later
Basic Workflow¶
The certificate issuance process follows these steps:
Create an ACME client and account
Create a certificate order
Complete domain validation challenges
Finalize the order and download the certificate
Step 1: Create Client and Account¶
from pathlib import Path
from acmeow import AcmeClient
# Create client (use staging for testing!)
client = AcmeClient(
server_url="https://acme-staging-v02.api.letsencrypt.org/directory",
email="admin@example.com",
storage_path=Path("./acme_data"),
)
# Create or retrieve account
client.create_account()
Warning
Use the staging server for testing to avoid rate limits. Switch to the production server only when ready.
Step 2: Create Certificate Order¶
from acmeow import Identifier
# Order certificate for one or more domains
order = client.create_order([
Identifier.dns("example.com"),
Identifier.dns("www.example.com"),
])
Step 3: Complete Challenges¶
For DNS-01 challenges (recommended for wildcards):
from acmeow import CallbackDnsHandler, ChallengeType
def create_txt_record(domain: str, record_name: str, value: str) -> None:
# record_name is "_acme-challenge.example.com"
# value is the TXT record content
print(f"Create TXT record: {record_name} = {value}")
# Use your DNS provider's API here
def delete_txt_record(domain: str, record_name: str) -> None:
print(f"Delete TXT record: {record_name}")
# Use your DNS provider's API here
handler = CallbackDnsHandler(
create_record=create_txt_record,
delete_record=delete_txt_record,
propagation_delay=120, # Wait for DNS propagation
)
client.complete_challenges(handler, ChallengeType.DNS)
Step 4: Finalize and Get Certificate¶
from acmeow import KeyType
# Generate key and submit CSR
client.finalize_order(KeyType.EC256)
# Download certificate
cert_pem, key_pem = client.get_certificate()
# Save to files
Path("certificate.pem").write_text(cert_pem)
Path("private_key.pem").write_text(key_pem)
Using an External CSR¶
If you manage your own private key (e.g., from a hardware security module or an external tool), you can pass a pre-generated CSR instead of having ACMEOW create one:
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.x509.oid import NameOID
# Generate key and CSR externally
my_key = ec.generate_private_key(ec.SECP256R1())
csr = (
x509.CertificateSigningRequestBuilder()
.subject_name(x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, "example.com"),
]))
.add_extension(
x509.SubjectAlternativeName([x509.DNSName("example.com")]),
critical=False,
)
.sign(my_key, hashes.SHA256())
)
csr_pem = csr.public_bytes(serialization.Encoding.PEM)
# Finalize with external CSR — no key is generated or stored by ACMEOW
client.finalize_order(csr=csr_pem)
# key_pem is None because the key was not managed by ACMEOW
cert_pem, _ = client.get_certificate()
Both PEM (str or bytes) and DER (bytes) encoded CSRs are accepted.
When a CSR is provided, the key_type and common_name parameters are
ignored.
Complete Example¶
from pathlib import Path
from acmeow import (
AcmeClient, Identifier, KeyType,
CallbackDnsHandler, ChallengeType,
)
# Initialize
client = AcmeClient(
server_url="https://acme-staging-v02.api.letsencrypt.org/directory",
email="admin@example.com",
storage_path=Path("./acme_data"),
)
# Account
client.create_account()
# Order
order = client.create_order([Identifier.dns("example.com")])
# Challenges
def create_record(domain, name, value):
print(f"CREATE: {name} TXT {value}")
input("Press Enter when record is created...")
def delete_record(domain, name):
print(f"DELETE: {name}")
handler = CallbackDnsHandler(create_record, delete_record)
client.complete_challenges(handler, ChallengeType.DNS)
# Finalize
client.finalize_order(KeyType.EC256)
cert_pem, key_pem = client.get_certificate()
print("Certificate obtained successfully!")