Fly Proxy terminates TLS connections at the edge, decrypting traffic before it reaches your application, which is running unencrypted internally.
Let’s see this in action. Imagine you have a simple web app deployed on Fly.io.
# First, deploy your app (assuming you have a Dockerfile and fly.toml)
fly deploy
# Once deployed, get its public URL
flyctl apps show <your-app-name>
# Example output:
# App Name: my-web-app
# Owner ID: ...
# ...
# Organization: ...
# Machines:
# ...
# IP Addresses:
# - 1.2.3.4
# - 2001:db8::1
# ...
# Domains:
# - my-web-app.fly.dev
Now, if you visit https://my-web-app.fly.dev in your browser, Fly Proxy handles the TLS handshake. Your browser connects to Fly’s edge infrastructure, presents its TLS certificate, and Fly’s proxy validates it and establishes a secure, encrypted connection.
After the decryption, Fly Proxy forwards the HTTP request to your application running on a Fly machine. Crucially, this internal connection between the proxy and your app is not TLS-encrypted by default. It’s just plain HTTP.
This setup offers several advantages. For your application, it means you don’t have to manage TLS certificates or deal with the overhead of encryption/decryption in your app code. You can run your web server on http://0.0.0.0:PORT (where PORT is typically 8080 or whatever your app listens on) without worrying about TLS.
Fly Proxy handles the public-facing TLS termination. It automatically provisions and renews Let’s Encrypt certificates for your *.fly.dev domains, and any custom domains you’ve added. This is managed by the flyctl command and the Fly platform itself.
The internal routing works by the proxy inspecting the request’s Host header and directing it to the appropriate application instance based on your fly.toml configuration. If you have multiple apps or services, the proxy ensures traffic gets to the right place.
The key lever you control is how your application listens for incoming connections. By default, Fly applications are expected to listen on 0.0.0.0:<PORT>, where <PORT> is usually 8080. The proxy knows to forward traffic to this port. You can configure this in your fly.toml file within the [[services]] section, specifically the internal_port directive.
# fly.toml example
app = "my-web-app"
primary_region = "ord"
[build]
image = "your-docker-image"
[[services]]
internal_port = 8080 # Your app listens on this port internally
protocol = "http"
[services.concurrency]
type = "connections"
hard_limit = 100
soft_limit = 80
[[services.ports]]
handlers = ["http"]
port = 80 # This is the port the proxy listens on for HTTP traffic
[[services.ports]]
handlers = ["https"]
port = 443 # This is the port the proxy listens on for HTTPS traffic
The internal_port in [[services]] tells Fly Proxy which port your application is listening on inside the machine. The [[services.ports]] sections define the public-facing ports (80 for HTTP, 443 for HTTPS) and the handlers for those ports. Fly Proxy maps incoming traffic on 443 (HTTPS) to your internal_port (8080) using HTTP.
It’s worth noting that while the default is plain HTTP internally, you can configure Fly Proxy to use TLS for internal connections if your application explicitly supports it and you’ve set up the necessary certificates. This is an advanced configuration and not the common case for simple web apps. The primary benefit of Fly Proxy is abstracting away TLS management for the developer.
The next thing you’ll likely encounter is how to handle custom domains and the specific DNS records you need to configure.