Containers and platforms

Fly.io custom domain TLS certificates with fly certs

By · Last verified

Fly.io issues a Let's Encrypt certificate for each custom domain you attach with fly certs add, then renews it for you. Validation runs automatically through a TLS-ALPN or HTTP-01 challenge once your A/AAAA or CNAME records point at the app. Wildcards need a DNS-01 _acme-challenge CNAME.

You can also import your own certificate with fly certs import. Single-hostname certificates cost $0.10 per month after the first 10 per organization; wildcards cost $1 per month. Steps follow the Fly.io documentation as of November 2026; my-app and example.com are examples.

At a glance

Question Fly.io
CA for automatic certificates Let's Encrypt
Validation TLS-ALPN (preferred), HTTP-01, or DNS-01
Apex (example.com) A and AAAA to the app's IPs (recommended)
www and other subdomains CNAME to the app's unique fly.dev target
CAA must allow letsencrypt.org
Wildcard certificates Yes, DNS-01 via _acme-challenge CNAME; $1/month
Upload your own certificate Yes, fly certs import
Certificate statuses active, pending_validation, pending_ownership

Step 1: Attach the domain

Run fly certs add once per hostname, including the apex and www:

fly certs add example.com -a my-app
fly certs add www.example.com -a my-app
fly certs add "*.example.com" -a my-app   # quote wildcards to avoid shell expansion

Each command prints the DNS options for your setup. You can see them again later with fly certs setup example.com, and the dashboard shows the same under Certificates.

If the app has no public IPv4 and IPv6 addresses yet, allocate the recommended ones (fly ips allocate-v4 and fly ips allocate-v6 exist for finer control):

fly ips allocate -a my-app
fly ips list -a my-app

Step 2: Create the DNS records

Fly.io can only issue or renew after verifying the domain through at least one of: an AAAA record pointing at the app, an _acme-challenge CNAME, or a _fly-ownership TXT record.

Name Record Value
example.com A the app's IPv4 address from fly certs setup
example.com AAAA the app's IPv6 address
www.example.com CNAME the app's unique target, shown by fly certs setup

A and AAAA records are Fly.io's recommendation for most cases, and the only sensible choice at the apex unless your provider flattens CNAMEs (ANAME/ALIAS). CNAMEs suit subdomains and apps whose IPs may change.

Two optional records solve special cases:

  • _acme-challenge CNAME for the DNS-01 challenge, required for wildcards and useful when you want the certificate before moving traffic.
  • _fly-ownership TXT to prove ownership when the domain is behind a CDN or proxy, when you import a certificate, or when you do not want to use IPv6. Several values can go in one record separated by semicolons.

Step 3: Check CAA records

If the domain publishes CAA records, Let's Encrypt must be allowed:

example.com.  3600  IN  CAA  0 issue "letsencrypt.org"

Test with the CAA checker. A CAA record that names only another CA stops issuance outright.

Step 4: Watch validation

fly certs check example.com -a my-app
fly certs list -a my-app

fly certs check re-runs DNS and validation checks. The Machines API exposes the same certificate states: active when the certificate is issued, pending_validation while a challenge is outstanding, and pending_ownership while ownership is unproven. Responses can also carry validation_errors and a rate_limited_until timestamp.

Using your own certificate

fly certs import uploads your own PEM full chain and key:

fly certs import example.com --fullchain fullchain.pem --private-key private-key.pem -a my-app

The status may show pending_ownership until an AAAA record or the _fly-ownership TXT record proves ownership. Custom and Fly-managed certificates can coexist on one hostname: the custom one is served first and the Let's Encrypt one is the fallback. Fly.io's automatic renewal covers the Let's Encrypt certificate it manages; an imported certificate has to be replaced by you before it expires. To remove only one kind, use fly certs remove example.com --custom or --acme.

Verify

A verified hostname shows the status active and presents a Let's Encrypt certificate (or your imported one). Check it with the SSL checker, or:

curl ssl.sh/example.com

Then confirm what Fly Proxy serves:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -issuer -subject -ext subjectAltName -dates

Expect a Let's Encrypt issuer for managed certificates, or your CA for an imported one.

Renewal

Fly.io renews the certificates it manages; the docs note that renewals do not count against Let's Encrypt's certificates-per-domain limit. Renewal needs the same verification as issuance, so keep the AAAA record, _acme-challenge CNAME or _fly-ownership TXT in place. Imported certificates are your job, and lifetimes are shrinking: 200 days today, 100 days from March 15, 2027 and 47 days from March 15, 2029. Track both kinds with ssl.sh monitoring (in development).

Troubleshooting

Creation or validation hangs, stalls or fails. Fly.io points first at Let's Encrypt rate limits:

  • Certificates per Registered Domain: 50 per week
  • Duplicate Certificate limit: 5 per week
  • Failed Validation limit: 5 failures per hostname, per hour

Fly.io cannot reset them; wait for the window to pass. Run Let's Debug against the domain to catch DNS, nameserver, CAA and networking problems, and avoid deleting and re-adding certificates while testing. See Let's Encrypt rate limits.

Behind Cloudflare. With the orange-cloud proxy on, add the _fly-ownership TXT record so Fly.io can issue through HTTP-01, and set Cloudflare's SSL mode to Full or Full (Strict); Flexible causes redirect loops. DNS-01 certificates conflict with Cloudflare Universal SSL, which inserts hidden _acme-challenge TXT records. If issuance still fails, import a Cloudflare Origin Certificate and verify ownership with the same TXT record.

CAA errors. Add 0 issue "letsencrypt.org"; see CAA record prevents issuance.

Wrong certificate in the browser. Check fly certs list: the hostname may be missing from the app, or a conflicting DNS record sends some visitors elsewhere.

FAQ

Which CA does Fly.io use for custom domains?

Let's Encrypt. Fly.io manages issuance and renewal for certificates added with fly certs add. Certificates you upload with fly certs import can come from any CA.

How much do Fly.io certificates cost?

Fly.io's pricing page lists $0.10 per month per single-hostname certificate and $1 per month per wildcard certificate, with the first 10 single-hostname certificates in each organization free.

Why is my certificate stuck in pending_validation?

Fly.io could not complete a challenge. Check that the A/AAAA or CNAME records match fly certs setup, that no CAA record excludes Let's Encrypt, and that you have not hit a Let's Encrypt rate limit. Run fly certs check example.com after fixing DNS.

Can I keep my own certificate and Fly's Let's Encrypt certificate on the same hostname?

Yes. When both exist, Fly.io serves your custom certificate as the primary one and keeps the Fly-managed Let's Encrypt certificate as an automatic fallback.

Sources

  1. Fly.io Docs: Custom domains
  2. Fly.io Docs: Understanding Cloudflare
  3. Fly.io Docs: Certificates (Machines API)
  4. Fly.io Docs: Pricing
  5. Let's Encrypt: Rate Limits