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

  1. 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)
    • Why it works: This record directly tells the ACME server that _acme-challenge.app.mydomain.com is an alias for _acme-challenge.my-awesome-app.fly.dev, allowing verification.
  2. Incorrect CNAME Name: Typing _acme-challenge.app.mydomain.com instead of _acme-challenge.app in 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.com to form the fully qualified domain name (FQDN).
    • Why it works: The ACME challenge is specifically for the _acme-challenge subdomain under your custom domain. The DNS provider needs to correctly map that specific subdomain.
  3. 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.
  4. CNAME Record Interfering with Other Records: If you have an A or AAAA record for app.mydomain.com and then try to add a CNAME for app.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-challenge record itself, but it’s a general DNS rule to be aware of.

    • Diagnosis: Check for A/AAAA records for app.mydomain.com in your DNS provider.
    • Fix: This is usually not an issue for the _acme-challenge record. If you were trying to point app.mydomain.com directly via CNAME to my-awesome-app.fly.dev, Fly.io’s instructions would be different (and you’d still need the _acme-challenge CNAME). For the certificate, focus only on the _acme-challenge CNAME.
  5. DNS Propagation Delay: DNS changes can take time to propagate across the internet.

    • Diagnosis: Use a tool like dig or an online DNS checker to see if the _acme-challenge.app.mydomain.com CNAME record is resolving correctly.
      dig _acme-challenge.app.mydomain.com CNAME +short
      
      This should output _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 dig shows the correct record, re-trigger the certificate creation on Fly.io (sometimes flyctl certs create will 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.
  6. Wildcard Certificates and _acme-challenge: If you’re trying to set up a wildcard certificate (e.g., *.mydomain.com), the _acme-challenge record needs to be set up slightly differently. Fly.io will guide you. For a single subdomain like app.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.com command is for specific subdomains.
    • Fix: Use flyctl certs create app.mydomain.com for specific subdomains. If you need a wildcard, use flyctl certs create *.mydomain.com. The _acme-challenge CNAME value will be different for wildcards.
    • Why it works: Wildcard certificate validation uses a different ACME challenge mechanism that requires the _acme-challenge record to point to a different value, specific to wildcard provisioning.

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.

Want structured learning?

Take the full Fly-io course →