ccnames.dev

cnames.dev / blog

Tutorial12 min read

How to add custom domains to a Fly.io app

Issue certificates for customer domains on Fly.io with fly certs: A/AAAA + _acme-challenge records, shared vs dedicated IPs, and the apex case. DIY and the easy way.

S
Saeed
July 5, 2026

Adding custom domains to a Fly.io app centers on one command: fly certs add. Fly requests a Let's Encrypt certificate for the hostname and tells you exactly which DNS records the customer needs. Here is the full DIY loop and the managed alternative when you outgrow it.

# Ask Fly to issue a cert for the customer hostname
fly certs add app.customer.com -a your-app

# Get the exact records + validation status
fly certs show app.customer.com -a your-app

Step 1 — Request the certificate

fly certs add registers the hostname with your app and kicks off issuance. Nothing serves yet — you need DNS pointing at Fly first.

Step 2 — Set the DNS records fly certs show tells you

Do not guess the records — fly certs show prints the precise values. For a subdomain the simplest path is a CNAME to your app; for an apex you use A/AAAA plus an _acme-challenge CNAME so Fly can validate and renew:

# subdomain (simplest)
app.customer.com.             CNAME  your-app.fly.dev.

# apex + ACME validation (values come from 'fly certs show')
customer.com.                 A      <shared-or-dedicated-ipv4>
customer.com.                 AAAA   <ipv6>
_acme-challenge.customer.com. CNAME  customer.com.<hash>.flydns.net.

The apex needs A/AAAA because of the CNAME-at-apex problem. Confirm the records resolve with the CNAME checker and SSL checker.

Step 3 — Shared vs dedicated IPs

A shared IPv4 is fine for HTTPS: Fly's edge routes by SNI, so many apps share one address. Allocate a dedicated IPv4 only when a customer needs a plain A record to a stable, non-shared IP, or for non-HTTP traffic:

fly ips allocate-v4 -a your-app     # dedicated (uses your quota)
# IPv6 is allocated by default and free

Step 4 — Route by host

const host = req.headers.get('host') ?? '';
const tenant = await getTenantByDomain(host);
if (!tenant) return new Response('Unknown domain', { status: 404 }); // fail closed

Fly sits in front of your app, so forward and trust the client IP the same way you would behind any proxy — see the framework guides like Node/Express for the trust proxy details.

Where the DIY path strains

Each domain is its own fly certs add plus DNS plus a status poll. At a few hundred customers you are scripting issuance, handling retries, and bumping into Let's Encrypt rate limits — the exact problems we detail in how we run SSL for 5,000+ domains. Compare building it yourself in cnames.dev vs self-hosting Caddy on-demand TLS.

The one-API-call way

curl -X POST https://api.cnames.dev/v1/domains \
  -H "Authorization: Bearer sk_live_..." \
  -d '{"hostname":"app.customer.com","origin":"your-app.fly.dev"}'

cnames.dev issues and renews certificates, handles rate-limit-aware retries, and forwards the real client IP to your Fly app. The full architecture is in the complete guide.

Building a SaaS that needs custom domains? cnames.dev gives every customer their own domain — SSL, edge routing, and per-tenant isolation in one API call. Free for 25 domains, no demo call. Start free · Read the docs

Frequently asked questions

How do I add a custom domain certificate on Fly.io?

Run "fly certs add <hostname>". Fly requests a Let's Encrypt certificate and tells you which DNS records to create. Run "fly certs show <hostname>" to see the exact A/AAAA and _acme-challenge values and the validation status.

Do I need a dedicated IP on Fly.io for custom domains?

Not necessarily. A shared IPv4 works for HTTPS because Fly routes by SNI. You allocate a dedicated IPv4 (fly ips allocate-v4) mainly if a customer needs a plain A record to a stable, non-shared address or you need non-HTTP traffic. IPv6 is free and allocated by default.

How do I script this for many customer domains?

Each hostname is its own "fly certs add" plus its DNS records, so you script the loop and poll "fly certs show" for status. Past a certain scale the per-hostname management and Let's Encrypt rate-limit handling is why teams move to a managed custom-domains layer.

Keep reading

S

SaeedFounder, cnames.dev — runs custom-domain infra for 5,000+ production sites at Lindo.ai.