Migrate to RunxBuild today!! Deploy More while Spending Less

Calculate your savings
unxBuild
Back to Blog Troubleshooting

HTTPS on Fly: Certificates, force_https and Redirect Loops Explained

Sean

Platform Writer

Oct 01, 2026
8 min read

To serve HTTPS on a Fly.io app you add a certificate for your domain with flyctl, set force_https in the http_service section of fly.toml so plain HTTP is redirected, and make sure your application trusts the X-Forwarded-Proto header instead of redirecting on its own.

HTTPS on Fly: Certificates, force_https and Redirect Loops Explained

The certificate part works almost by itself. The redirect loops and mixed signals come from the app and the proxy disagreeing about the scheme.

Table of contents

How TLS works on this platform

A request to an app on Fly.io does not reach your process directly. It first hits the platform’s edge proxy, which terminates TLS, picks the right machine and forwards the request to your app over the internal network. Because of that, your app usually receives plain HTTP on its internal port, even though the user’s connection was HTTPS from end to end as far as the browser can tell.

That is a common arrangement for platforms and load balancers, and it has two consequences. First, you do not install certificates in your own server. The platform issues and renews them. Second, your application cannot learn the original scheme from the connection itself, so it must read a header the proxy sets.

The default hostname for an app, ending in the platform’s own domain, already works over HTTPS. A custom domain needs a certificate added for it. The community threads on the topic include requests to use your own certificate, and the typical answer is that the platform’s built-in certificate mechanism is the supported route, so plan around that rather than around uploading your own.

A note on accuracy: platform behaviour and flags change, so treat this post as a map and confirm exact commands against the current documentation of the platform you use.

Add a certificate for a custom domain

Point DNS first, then ask for the certificate. The platform validates that the domain really points at your app before it issues anything.

fly certs add www.example.com
fly certs show www.example.com

The second command shows the status and the DNS records the platform expects. For an apex domain you typically create address records, and for a subdomain a CNAME record pointing at your app’s hostname. Wait for DNS to propagate, and the status moves to issued. If it stalls, check that you have no conflicting records, and that no CAA record forbids the issuing authority.

You can add both the apex and www, and then choose one as canonical and redirect the other to it. Test with curl rather than only a browser, because browsers cache redirects and HSTS aggressively and can mislead you.

curl -sI https://www.example.com | head -n 5
curl -sI http://www.example.com | head -n 5

The first should return a success status with a valid certificate. The second, once redirect is configured, should return a redirect to the HTTPS URL. If the certificate step fails with a validation error, nine times out of ten it is a DNS record that has not propagated or a stray record that points elsewhere.

Force HTTPS in fly.toml

To redirect plain HTTP requests to HTTPS you do not need application code. The configuration file has a setting inside the http_service section.

[http_service]
  internal_port = 8080
  force_https = true
  auto_stop_machines = "stop"
  auto_start_machines = true
  min_machines_running = 0

With force_https = true, the edge proxy answers HTTP requests with a redirect to the HTTPS version of the same URL before they reach your app. This is the cleanest option, because the redirect happens at the proxy and costs your application nothing.

Make sure the internal_port matches the port your process actually listens on, and that your process binds to all interfaces, not just localhost. A mismatch there produces errors that look like TLS problems but are really an unreachable app.

Redeploy after changing the file. Then repeat the two curl checks. If both behave, you are done for most apps. The community thread on using a CDN proxy with the platform’s certificates describes exactly this combination of platform certificate plus force_https providing the HTTP to HTTPS redirect.

When your app also redirects: loops

The classic failure is an infinite redirect. It happens when your framework also tries to force HTTPS, but it sees the internal plain HTTP connection and decides the request is insecure. It redirects to HTTPS, the proxy forwards that request internally as HTTP again, and the cycle never ends.

The fix is to make the app trust the forwarded protocol header that the proxy sets, X-Forwarded-Proto. The platform’s own blog shows the idea in several languages. In Node with Express, for instance:

app.set('trust proxy', true);
app.use((req, res, next) => {
  if (req.get('X-Forwarded-Proto') === 'http') {
    return res.redirect(301, 'https://' + req.headers.host + req.url);
  }
  next();
});

Frameworks have settings for this. In Django you set a secure proxy SSL header setting. In Rails you set force_ssl and allow it to trust the proxy. In Spring you configure forwarded header handling. The principle is identical: the app should look at the header, not at the socket.

Simplest of all: turn off HTTPS redirection in the app and let the proxy’s force_https do it. One layer should own the redirect. Two layers owning it is how loops happen.

For deeper background on the symptom, see the post on too many HTTP redirects.

Using a CDN or proxy in front

Many sites put another proxy in front of the platform for caching or protection. That adds a second TLS hop, and mistakes there look like platform problems.

The rule is to encrypt every leg and verify it. If the front proxy offers modes where it speaks plain HTTP to the origin, do not use them, since they leave the origin leg unprotected and can cause the same redirect loops described above, because the app sees HTTP coming from the proxy.

Choose the strict mode that validates the origin certificate, and make sure the platform has issued a certificate for the hostname the front proxy connects to. Certificate issuance by the platform may need the domain to resolve directly to the app at validation time. If the front proxy hides the origin, issuance can fail until you temporarily switch the record to DNS only, or use a validation method that works through the proxy. The community discussion of exactly this combination is a good read before you set it up.

Test the whole chain from outside, and test the origin directly where possible. Where two TLS layers exist, check redirects from both layers’ point of view.

Mixed content, HSTS and other follow-ups

Once HTTPS works, several second-order issues appear.

Mixed content. A page served over HTTPS that loads an image or script over plain HTTP is blocked or flagged by browsers. Search your templates and database for hard-coded http:// URLs and switch them to HTTPS or protocol-relative paths.

HSTS. The strict transport security header tells browsers to use HTTPS for your domain for a set time. Turn it on after you are sure everything works, starting with a short lifetime, because a wrong HSTS setting is hard to undo for visitors who have cached it.

Canonical URLs and sitemaps. Update them to the HTTPS version so search engines consolidate on one address.

Webhooks and OAuth callbacks. Providers store exact URLs. If you registered http addresses, update them to https, or callbacks will fail after the redirect.

Cookies. Mark session cookies Secure so they are never sent over plain HTTP.

Run a quick audit with browser developer tools open: the console lists blocked mixed content, and the security tab shows the certificate state.

The same job on other hosts

Every platform reaches the same four jobs by a different route: obtain and renew a certificate, terminate TLS at an edge, redirect HTTP to HTTPS, and tell the app the original scheme.

On RunxBuild, static sites and services support custom domains with certificates handled, so you add the domain, create the DNS records the dashboard shows, and the certificate is issued without you managing renewal. Headers and redirects for static sites are set in configuration. The details are in the custom domains and certificates docs and the redirects docs. For an application behind it, the same rule holds: trust the forwarded protocol header rather than the connection. Learn the pattern once, and it applies wherever you deploy.

How this fits the rest of the stack

HTTPS itself is free, and the machines that serve it are not. Before you move or add an app, line up the service and the domain traffic in the RunxBuild hosting calculator. When you are ready to try certificates and domains handled for you, the RunxBuild dashboard is where a site or service is created.

Useful related references:

FAQ

How do I get HTTPS for a custom domain on Fly?

Point the domain at your app with the DNS records the platform specifies, then run fly certs add for the domain. Check status with fly certs show until the certificate is issued.

What does force_https do in fly.toml?

Inside the http_service section it makes the edge proxy redirect HTTP requests to HTTPS before they reach your app, so you do not need redirect code in the application.

Why do I get a redirect loop after enabling HTTPS?

Usually the app also redirects to HTTPS but sees the internal plain HTTP connection. Make the app trust X-Forwarded-Proto, or remove the app-level redirect and let the proxy handle it.

Can I use my own certificate?

The platform’s built-in managed certificates are the supported path for most users, so plan around them. Check the current documentation for any option to bring your own.

Can I put a CDN in front of the platform?

Yes, but encrypt and verify both legs, avoid modes that speak plain HTTP to the origin, and make sure certificate issuance can still validate your domain.

#https fly#Fly.io certificates#force_https#X-Forwarded-Proto#Custom domain SSL