Notes
Generating Cloudflare Origin Certificate for Multiple Domains
Cloudflare recommends having an Origin Certificate installed on the server that hosts your website (your Origin) so that requests between Cloudflare and your Origin are encrypted and Cloudflare can authenticate your server’s data.
This gets problematic if you host multiple domains on your server. It’s not possible to do so through the UI, but Cloudflare actually supports multi-domain Origin Certificates through its API. To generate and install a multi-domain certificate, use this script:
"""
This script generates an origin certificate from Cloudlare using their API.
It requires the 'requests' library to make HTTP requests.
"""
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
import requests
# Origin SSL Certificate Update API Token
# Generate at https://dash.cloudflare.com/profile/api-tokens
# Create a Token -> Create Custom Token
# Settings: Permissions Zone + SSL and Certificates + Edit
AUTH_TOKEN = "<TOKEN>"
INSTRUCTIONS = """
sudo cp certificates/server.key /etc/nginx/ssl/server.key
sudo cp certificates/server.pem /etc/nginx/ssl/server.pem
sudo chmod 640 /etc/nginx/ssl/server.key
# Restart nginx
# /etc/init.d/nginx restart
"""
def get_domains() -> list[str]:
return [
"example.com",
"example2.com",
"example3.com",
]
def generate_key() -> rsa.RSAPrivateKey:
# Generate our key
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
# Write our key to disk for safe keeping
with open("certificates/server.key", "wb") as f:
private_bytes = key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
print("Private Key for signing")
print(private_bytes.decode("utf-8"))
f.write(private_bytes)
return key
def generate_csr(key: rsa.RSAPrivateKey) -> str:
# Generate a CSR
csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
# Provide various details about who we are.
x509.NameAttribute(NameOID.COUNTRY_NAME, "<COUNTRY>"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "<STATE>"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "<CITY>"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "<ORGANIZATION>"),
x509.NameAttribute(NameOID.COMMON_NAME, "<COMMON_NAME>"),
])).sign(key, hashes.SHA256())
csr_bytes = csr.public_bytes(serialization.Encoding.PEM)
with open("certificates/csr.pem", "wb") as f:
f.write(csr_bytes)
csr_string = csr_bytes.decode("utf-8")
print("Certificate Signing Request:")
print(csr_string)
return csr_string
def request_origin_certificate(csr: str, domains: list[str]) -> None:
# Request the origin certificate from Cloudflare
url = "https://api.cloudflare.com/client/v4/certificates"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer %s" % AUTH_TOKEN,
}
domains += ["*.%s" % d for d in domains]
print("Domains:")
print(domains)
data = {
"csr": csr,
"hostnames": domains,
"request_type": "origin-rsa",
"requested_validity": 5475, # Valid for 15 years
}
response = requests.post(url, json=data, headers=headers)
if response.status_code == 200:
print("Origin certificate requested successfully.")
data = response.json()
certificate_pem = data["result"]["certificate"] # type: ignore
with open("certificates/server.pem", "w") as f:
f.write(certificate_pem.strip())
print("Origin certificate:")
print(certificate_pem)
else:
print("Failed to request origin certificate.")
print("Status Code:", response.status_code)
print(response.text)
def main() -> None:
domains = get_domains()
key = generate_key()
csr_string = generate_csr(key)
request_origin_certificate(csr_string, domains)
print(INSTRUCTIONS)
if __name__ == "__main__":
main()