Fly.io handles SSL certificates automatically, but adding your own custom domain requires a specific DNS configuration that, if done incorrectly, can lead to a frustrating loop of certificate provisioning failures.
Let’s see this in action. Imagine you’ve deployed your app to my-awesome-app.fly.dev. You want it to be accessible at app.mydomain.com.
First, on Fly.io, you’d run:
flyctl certs create app.mydomain.com
This command tells Fly.io to start the process of obtaining an SSL certificate for app.mydomain.com. Fly.io will respond with instructions, typically looking something like this:
Successfully added certificate for app.mydomain.com
Please add the following CNAME record to your DNS provider:
Type: CNAME
Name: _acme-challenge.app.mydomain.com
Value: "_acme-challenge.my-awesome-app.fly.dev."
This is where things can go sideways. The _acme-challenge subdomain is part of the ACME (Automated Certificate Management Environment) protocol, which Let’s Encrypt uses to verify domain ownership. Fly.io delegates this verification to your DNS provider.
The DNS Dance
Your DNS provider is the gatekeeper. Fly.io needs to confirm that you control app.mydomain.com by asking your DNS provider to answer a specific challenge. The _acme-challenge CNAME record is that challenge.
If Fly.io can’t resolve this CNAME record to the specified value (_acme-challenge.my-awesome-app.fly.dev.), it can’t prove you own the domain, and the certificate won’t be issued. This usually results in an error like:
Error: certificate for app.mydomain.com failed to provision. Please check your DNS configuration.
Common Pitfalls and Their Fixes
-
Missing CNAME Record: The most straightforward mistake is simply not adding the CNAME record at all.
- Diagnosis: Log into your DNS provider (e.g., Cloudflare, Namecheap, GoDaddy) and check the DNS records for
mydomain.com. Look for a record matching_acme-challenge.app. - Fix: Add the CNAME record exactly as Fly.io specified.
- Type:
CNAME - Name:
_acme-challenge.app(your DNS provider might auto-append the domain, so you might only need to enter_acme-challenge.app) - Value:
_acme-challenge.my-awesome-app.fly.dev.(ensure the trailing dot is present if your provider requires it for FQDNs)
- Type:
- Why it works: This record directly tells the ACME server that
_acme-challenge.app.mydomain.comis an alias for_acme-challenge.my-awesome-app.fly.dev, allowing verification.
- Diagnosis: Log into your DNS provider (e.g., Cloudflare, Namecheap, GoDaddy) and check the DNS records for
-
Incorrect CNAME Name: Typing
_acme-challenge.app.mydomain.cominstead of_acme-challenge.appin the "Name" field, or vice-versa.- Diagnosis: Double-check the "Name" field in your DNS provider’s interface against the output from
flyctl certs create. - Fix: Correct the "Name" field to
_acme-challenge.app. The DNS provider will automatically append.mydomain.comto form the fully qualified domain name (FQDN). - Why it works: The ACME challenge is specifically for the
_acme-challengesubdomain under your custom domain. The DNS provider needs to correctly map that specific subdomain.
- Diagnosis: Double-check the "Name" field in your DNS provider’s interface against the output from
-
Incorrect CNAME Value: Typos in the value, or omitting the trailing dot for the FQDN.
- Diagnosis: Verify the "Value" field matches
_acme-challenge.my-awesome-app.fly.dev.. Pay close attention to the trailing dot. - Fix: Ensure the value is precisely
_acme-challenge.my-awesome-app.fly.dev.. If your DNS provider automatically adds a trailing dot, you might be able to omit it from the input, but it’s generally safer to include it if the interface allows. - Why it works: The ACME server needs to resolve this value to a domain that Fly.io controls, which is confirmed by the trailing dot indicating a FQDN.
- Diagnosis: Verify the "Value" field matches
-
CNAME Record Interfering with Other Records: If you have an A or AAAA record for
app.mydomain.comand then try to add a CNAME forapp.mydomain.com, most DNS providers will disallow this. The ACME challenge, however, requires a CNAME for_acme-challenge.app.mydomain.com. This is less common for the_acme-challengerecord itself, but it’s a general DNS rule to be aware of.- Diagnosis: Check for A/AAAA records for
app.mydomain.comin your DNS provider. - Fix: This is usually not an issue for the
_acme-challengerecord. If you were trying to pointapp.mydomain.comdirectly via CNAME tomy-awesome-app.fly.dev, Fly.io’s instructions would be different (and you’d still need the_acme-challengeCNAME). For the certificate, focus only on the_acme-challengeCNAME.
- Diagnosis: Check for A/AAAA records for
-
DNS Propagation Delay: DNS changes can take time to propagate across the internet.
- Diagnosis: Use a tool like
digor an online DNS checker to see if the_acme-challenge.app.mydomain.comCNAME record is resolving correctly.
This should outputdig _acme-challenge.app.mydomain.com CNAME +short_acme-challenge.my-awesome-app.fly.dev.. If it doesn’t, wait longer. - Fix: Wait. DNS propagation can take anywhere from a few minutes to 48 hours, though it’s typically much faster. Once
digshows the correct record, re-trigger the certificate creation on Fly.io (sometimesflyctl certs createwill automatically retry, or you might need to delete and re-create). - Why it works: You’re giving the DNS system enough time to update its caches globally so that Fly.io’s servers can see the correct configuration.
- Diagnosis: Use a tool like
-
Wildcard Certificates and
_acme-challenge: If you’re trying to set up a wildcard certificate (e.g.,*.mydomain.com), the_acme-challengerecord needs to be set up slightly differently. Fly.io will guide you. For a single subdomain likeapp.mydomain.com, you don’t need a wildcard.- Diagnosis: Ensure you are not trying to create a wildcard certificate if you only need it for a specific subdomain. The
flyctl certs create app.mydomain.comcommand is for specific subdomains. - Fix: Use
flyctl certs create app.mydomain.comfor specific subdomains. If you need a wildcard, useflyctl certs create *.mydomain.com. The_acme-challengeCNAME value will be different for wildcards. - Why it works: Wildcard certificate validation uses a different ACME challenge mechanism that requires the
_acme-challengerecord to point to a different value, specific to wildcard provisioning.
- Diagnosis: Ensure you are not trying to create a wildcard certificate if you only need it for a specific subdomain. The
After your DNS records have propagated and are correctly configured, Fly.io will automatically retry the certificate provisioning. Once successful, you’ll see the certificate listed as Issued in your flyctl output or the Fly.io dashboard.
The next thing you’ll likely encounter is ensuring your application actually serves traffic over HTTPS by configuring your fly.toml to listen on the correct port and potentially redirecting HTTP to HTTPS.