1---
2title: IPs & certificates
3description: Public and private Fly Services, App addresses, and certificates for your own hostname.
4---
5
6A [Service](/fly/compute/services) owns its App and manages that
7App's addresses. It is public by default and reachable only inside
8your organization with `public: false`. Fly's proxy load-balances
9`{app}.fly.dev` across Machines that publish a proxy service.
10
11IPs and certificates you declare yourself attach to a
12[`Fly.App`](/fly/compute/apps): use them for Machines, for Services
13grouped in one App, and for your own domain.
14
15## Public Services
16
17A Service with `port` listens inside the Machine. Alchemy publishes
18HTTP 80 and HTTPS 443 on the Fly proxy in front of it, and allocates
19a shared IPv4 and an IPv6 on the Service's App. Both are free.
20
21```typescript
22export default class Api extends Fly.Service<Api>()(
23 "Api",
24 { main: import.meta.url, region: "iad", port: 3000 },
25 Effect.gen(function* () {
26 return {
27 fetch: Effect.succeed(HttpServerResponse.text("hello")),
28 };
29 }),
30) {}
31```
32
33Yield the Service in the Stack. `api.url` is
34`https://{appName}.fly.dev`.
35
36```typescript
37export default Alchemy.Stack(
38 "MyApp",
39 { providers: Fly.providers(), state: Alchemy.localState() },
40 Effect.gen(function* () {
41 const api = yield* Api;
42 return { url: api.url };
43 }),
44);
45```
46
47Fly's proxy terminates TLS on 443. The Service still listens on
48`port` inside the Machine.
49
50## Private Services with Flycast
51
52`public: false` keeps a Service off the internet. Its App gets only a
53free Flycast address, which Fly's proxy serves inside your
54organization's private network.
55
56```diff lang="typescript"
57export default class Api extends Fly.Service<Api>()(
58 "Api",
59- { main: import.meta.url, region: "iad", port: 3000 },
60+ { main: import.meta.url, region: "iad", port: 3000, public: false },
61```
62
63Fly issues no TLS certificate for `.flycast`, so a private Service
64publishes plain HTTP on port 80. `url` is `undefined` and
65`privateUrl` is `http://{appName}.flycast`.
66
67Other Services call it by binding it with `Fly.bindService`, which
68returns a typed client for its methods — see
69[Call one Service from another](/fly/compute/services#call-one-service-from-another).
70Bound calls go to `privateUrl` through Fly's proxy, so service checks,
71`autostart`, and the traffic switch of a
72[blue/green deployment](/fly/compute/deployments) apply. Only Services
73that bind the target receive its caller token, and the target checks
74Fly's `Fly-Src` signature, so a method answers only bound callers on
75Fly's private network. The traffic is plain HTTP inside Fly's
76WireGuard-encrypted network.
77
78Turning `public` on or off updates the Service in place. Its App and
79hostname stay the same.
80
81## Restrict private Services to the stack
82
83Fly's default private network spans the organization, so every App in
84the org can call a private Service. Set `network: yield* Fly.stackNetwork`
85on each Service to put them on a network unique to the stack and
86stage. Services on it bind each other; Apps on any other network,
87including the default one, get `bad address` for the `.flycast` and
88`.internal` names, and binding across networks fails the caller's
89deploy with `Fly.ServiceUnreachable`. A public Service on the network
90still serves `url`, so it becomes the stack's single entry point.
91[Connect Services](/fly/compute/connecting-services) walks through it.
92
93## Addresses for a shared App
94
95A [Machine](/fly/compute/machines), or a Service that joins a
96`Fly.App` with [`app`](/fly/compute/services#group-services-in-one-app),
97uses the addresses on that App. Alchemy manages none for it.
98`{app}.fly.dev` does not answer over IPv4 until the App has an
99[`IpAssignment`](/providers/fly/ipassignment). Allocate a shared
100Anycast IPv4:
101
102```typescript
103export const Site = Fly.App("Site");
104
105export const PublicIp = Fly.IpAssignment("Shared", {
106 app: Site,
107 type: "shared_v4",
108});
109```
110
111Yield it next to the resources in the App.
112
113```diff lang="typescript"
114Effect.gen(function* () {
115 const api = yield* Api;
116+ const ip = yield* PublicIp;
117 return { url: api.url, ip: ip.ip };
118}),
119```
120
121`v6` is free dedicated IPv6. `v4` is billed dedicated IPv4 and may
122400 if the org has no quota. Prefer `shared_v4` or `v6` in tests.
123
124`private_v6` is a free Flycast address that is not reachable from the
125internet. Allocate only `private_v6` on an App to keep everything in
126it private:
127
128```typescript
129export const Backend = Fly.App("Backend");
130
131export const BackendIp = Fly.IpAssignment("Flycast", {
132 app: Backend,
133 type: "private_v6",
134});
135```
136
137Resources in the App still publish a port for the proxy to forward
138to. Publish plain HTTP on port 80 without `forceHttps`:
139
140```typescript
141export default class Worker extends Fly.Service<Worker>()(
142 "Worker",
143 {
144 app: Backend,
145 main: import.meta.url,
146 port: 3000,
147 services: [{
148 protocol: "tcp",
149 internalPort: 3000,
150 ports: [{ port: 80, handlers: ["http"] }],
151 }],
152 },
153 /* ... */
154) {}
155```
156
157Other Apps on the App's network reach it at `http://{appName}.flycast`.
158A Service in the App is also [bindable](/fly/compute/services#bind-a-service-in-a-shared-app):
159its deploy adds the Flycast address itself.
160
161`network` places the address on a named private network instead of the
162organization default. The network must already exist: an App created with
163the same `network` creates it, and an unknown name fails with
164`NetworkNotFound`. Changing `network` replaces the assignment.
165
166## Use your own hostname
167
168A Certificate covers a hostname on a `Fly.App`. It cannot target a
169Service's own App yet, so run the Service in that App with
170[`app: Site`](/fly/compute/services#group-services-in-one-app) and
171allocate its addresses there. Default `kind` is `"acme"` (Let's
172Encrypt).
173
174```typescript
175export const V6 = Fly.IpAssignment("V6", {
176 app: Site,
177 type: "v6",
178});
179
180export const Www = Fly.Certificate("Www", {
181 app: Site,
182 hostname: "www.example.com",
183 kind: "acme",
184});
185```
186
187Yield `Www` in the Stack. Point DNS at the App. An A record for
188`www` to `PublicIp.ip`. An AAAA record to `V6.ip`. Plus whatever
189`Www.dnsRequirements` lists for the ACME challenge.
190
191Fly's proxy terminates TLS on 443 once the certificate is
192`configured`.
193
194`"custom"` uploads a PEM (`fullchain` + `privateKey`). Hostname is
195the identity. Changing `app`, `hostname`, or `kind` replaces.
196
197## Issue a certificate independently
198
199Use `alchemy/ACME` when you want to choose the certificate authority or
200issue a wildcard with DNS-01. The account and certificate are independent
201of Fly; this example uploads the result to a Fly App.
202
203```typescript
204import * as ACME from "alchemy/ACME";
205import * as Cloudflare from "alchemy/Cloudflare";
206import * as Fly from "alchemy/Fly";
207import * as Layer from "effect/Layer";
208
209const providers = Layer.mergeAll(
210 ACME.providers(),
211 Cloudflare.providers(),
212 Fly.providers(),
213);
214```
215
216Use `providers` in the Stack configuration. Inside the Stack's Effect,
217resolve your existing Cloudflare `Zone` and Fly `Site`, then issue and upload:
218
219```typescript
220const zone = yield* Zone;
221const site = yield* Site;
222const account = yield* ACME.Account("LetsEncrypt", {
223 ca: ACME.LetsEncrypt,
224 contact: ["mailto:ops@example.com"],
225 termsOfServiceAgreed: true,
226});
227const certificate = yield* ACME.Certificate("Wildcard", {
228 account,
229 identifiers: ["*.example.com"],
230 solver: Cloudflare.DNS.AcmeSolver(zone),
231});
232yield* Fly.Certificate("WildcardUpload", {
233 app: site,
234 hostname: "*.example.com",
235 kind: "custom",
236 fullchain: certificate.chain,
237 privateKey: certificate.privateKey,
238});
239```
240
241The DNS solver publishes and removes the challenge TXT records. Cloudflare's
242solver waits 60 seconds before validation so cached challenge values can expire.
243Start with `ACME.LetsEncryptStaging` while developing; its certificates are not
244trusted by browsers.
245
246Renewal is evaluated when you deploy, with a default threshold of 30 days before
247expiry. Schedule deployments if you need automatic renewal. Account and
248certificate private keys are persisted in stack state as redacted values;
249protect the state backend as secret material. Redaction alone is not encryption.
250
251## Manage certificates at runtime
252
253A Service can bind `Fly.WriteCertificates(Site)` to request, upload, inspect,
254check, and remove certificates without redeploying. Provide
255`Fly.WriteCertificatesHttp` on the Service Effect. For runtime issuance,
256`ACME.IssueCertificate` and `ACME.IssueCertificateHttp` bind an existing account;
257pass a runtime DNS solver to each issuance call.
258
259Runtime operations do not become stack resources. Your application owns renewal
260scheduling, revocation, and removal of certificates it creates this way. Verify
261that the chosen certificate authority is reachable from your runtime.
262
263Follow [Runtime issuance](/acme/runtime) for an authenticated Worker example,
264[Using certificates](/acme/using-certificates) for Fly uploads, and
265[Renewal & revocation](/acme/renewal) for scheduling and deletion policies.
266The [`IssueCertificate`](/providers/acme/issuecertificate) and
267[`WriteCertificates`](/providers/fly/writecertificates) references document the
268binding methods.
269
270## Where next
271
272The [tutorial](/fly/tutorial/part-1) deploys a public Service.
273[Connect Services](/fly/compute/connecting-services) binds private
274Services to each other on a stack network.
275[Services](/fly/compute/services) covers private Services and grouping
276Services in one [App](/fly/compute/apps). See the
277[`IpAssignment`](/providers/fly/ipassignment) and
278[`Certificate`](/providers/fly/certificate) references.