diff --git a/blueprints/per-user-dev-environments.html.md b/blueprints/per-user-dev-environments.html.md index 5321ebc3aa..0c0df40484 100644 --- a/blueprints/per-user-dev-environments.html.md +++ b/blueprints/per-user-dev-environments.html.md @@ -38,7 +38,7 @@ Your router app handles all incoming wildcard traffic. Its responsibility is sim - Look up the correct app (and optionally machine ID) for that user. - Issue a `fly-replay` header directing the Fly Proxy to [internally redirect the request](/docs/blueprints/connecting-to-user-machines/#using-fly-replay) (this should add no more than ~10 milliseconds of latency if the router app is deployed close to the user). - When appropriate, use [replay caching](/docs/networking/dynamic-request-routing/#replay-caching) to further reduce latency and load on the router app. -- Make sure you've added [a wildcard domain](/docs/networking/custom-domain/#get-certified) (*.example.com) to your router app (read more about the [certificate management endpoint here](/docs/networking/custom-domain-api/)). +- Make sure you've added [a wildcard domain](/docs/networking/custom-domain/#add-your-domain-to-your-app) (*.example.com) to your router app (read more about the [Certificates API reference](/docs/machines/api/certificates-resource/)). ### User apps diff --git a/machines/api/certificates-resource.html.markerb b/machines/api/certificates-resource.html.markerb new file mode 100644 index 0000000000..382f300266 --- /dev/null +++ b/machines/api/certificates-resource.html.markerb @@ -0,0 +1,700 @@ +--- +title: Certificates +layout: docs +nav: machines_toc +toc: false +redirect_from: + - /docs/networking/custom-domain-api/ +--- + +Use the Certificates resource to manage SSL/TLS certificates for custom domains on your Fly Apps. Fly.io can automatically issue Let's Encrypt certificates (ACME), or you can import your own custom certificates. When both exist for a hostname, the custom certificate is served as primary and the ACME certificate acts as an automatic fallback. Learn more about [custom domains](/docs/networking/custom-domain/). + +
+
+
+ Endpoints +
+ +
+
+ +## List certificates + +`GET /apps/{app_name}/certificates` + +List all certificates for an app, with optional filtering and cursor-based pagination. + +
+
+ <% api_info = ApiInfoComponent.new( + heading: 'Path parameters', + name: 'app_name', + type: 'string', + required: true, + description: 'The name of the Fly App.' + ) %> + <%= render(api_info) %> + <% api_info = ApiInfoComponent.new( + heading: 'Query parameters', + name: 'filter', + type: 'string', + required: false, + description: 'Filter certificates by hostname substring.' + ) %> + <% api_info.add_info( + name: 'cursor', + type: 'string', + required: false, + description: 'Pagination cursor from a previous response.' + ) %> + <% api_info.add_info( + name: 'limit', + type: 'integer', + required: false, + description: 'Maximum number of certificates to return.' + ) %> + <%= render(api_info) %> + <%= render(ApiInfoComponent.new( + heading: 'Responses', + name: '200', + description: 'OK' + )) %> +
+
+ <%= render(CodeToggleComponent.new(badge: 'GET', title: '/v1/apps/\{app_name\}/certificates')) do |component| %> + <% component.with_curl do %> + curl -i -X GET \ + -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \ + "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates" + <% end %> + <% end %> + <%= render(CodeSampleComponent.new(title: 'Status: 200 OK - Example response', language: 'json')) do %> +{ + "certificates": [ + { + "hostname": "example.com", + "status": "active", + "dns_provider": "enom", + "acme_dns_configured": true, + "acme_alpn_configured": true, + "acme_http_configured": true, + "ownership_txt_configured": true, + "configured": true, + "acme_requested": true, + "has_custom_certificate": false, + "has_fly_certificate": true, + "created_at": "2024-01-15T10:30:00Z", + "updated_at": "2024-01-15T12:00:00Z" + } + ], + "total_count": 1 +} + <% end %> +
+
+ +## Request ACME certificate + +`POST /apps/{app_name}/certificates/acme` + +Add a hostname to your app and request an automatic Let's Encrypt certificate. Fly.io will attempt to validate domain ownership and issue a certificate. Check the `dns_requirements` in the response to see what DNS records you need to configure. + +
+
+ <%= render(ApiInfoComponent.new( + heading: 'Path parameters', + name: 'app_name', + type: 'string', + required: true, + description: 'The name of the Fly App.' + )) %> + <%= render(ApiInfoComponent.new( + heading: 'Responses', + name: '201', + description: 'created' + )) %> +
+
+ <%= render(CodeToggleComponent.new(badge: 'POST', title: '/v1/apps/\{app_name\}/certificates/acme')) do |component| %> + <% component.with_curl do %> + curl -i -X POST \ + -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \ + "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates/acme" \ + -d '{ + "hostname": "example.com" + }' + <% end %> + <% component.with_json do %> + { + "hostname": "example.com" + } + <% end %> + <% component.with_json_table do %> + | Property | Type | Required | Description | + |------------|---------|----------|----------------------------| + | `hostname` | string | yes | The domain to issue a certificate for. | + <% end %> + <% end %> + <%= render(CodeSampleComponent.new(title: 'Status: 201 created - Example response', language: 'json')) do %> +{ + "hostname": "example.com", + "configured": false, + "acme_requested": true, + "status": "pending_validation", + "dns_provider": "enom", + "rate_limited_until": null, + "certificates": [], + "validation": { + "dns_configured": false, + "alpn_configured": false, + "http_configured": false, + "ownership_txt_configured": false + }, + "dns_requirements": { + "a": ["137.66.XXX.XXX"], + "aaaa": ["2a09:8280:1::XXXX"], + "cname": "my-app-name.fly.dev", + "acme_challenge": { + "name": "_acme-challenge.example.com", + "target": "example.com.XXXXX.flydns.net" + }, + "ownership": { + "name": "_fly-ownership.example.com", + "app_value": "app-XXXXXXXXXX", + "org_value": "org-XXXXXXXXXX" + } + }, + "validation_errors": [] +} + <% end %> +
+
+ +## Import custom certificate + +`POST /apps/{app_name}/certificates/custom` + +Upload your own certificate and private key in PEM format. The certificate must not be expired, the private key must match the certificate, and the hostname must appear in the certificate's Subject Alternative Names (SAN) or Common Name (CN). Wildcard certificates are supported. + +Domain ownership must be verified via a `_fly-ownership` DNS TXT record before the certificate becomes active. Check the `dns_requirements.ownership` field in the response for the required record. + +
+
+ <%= render(ApiInfoComponent.new( + heading: 'Path parameters', + name: 'app_name', + type: 'string', + required: true, + description: 'The name of the Fly App.' + )) %> + <%= render(ApiInfoComponent.new( + heading: 'Responses', + name: '201', + description: 'created' + )) %> +
+
+ <%= render(CodeToggleComponent.new(badge: 'POST', title: '/v1/apps/\{app_name\}/certificates/custom')) do |component| %> + <% component.with_curl do %> + curl -i -X POST \ + -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \ + "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates/custom" \ + -d '{ + "hostname": "example.com", + "fullchain": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----" + }' + <% end %> + <% component.with_json do %> + { + "hostname": "", + "fullchain": "", + "private_key": "" + } + <% end %> + <% component.with_json_table do %> + | Property | Type | Required | Description | + |---------------|---------|----------|----------------------------| + | `hostname` | string | yes | The domain for the certificate. | + | `fullchain` | string | yes | PEM-encoded certificate chain. | + | `private_key` | string | yes | PEM-encoded private key. | + <% end %> + <% end %> + <%= render(CodeSampleComponent.new(title: 'Status: 201 created - Example response', language: 'json')) do %> +{ + "hostname": "example.com", + "configured": false, + "acme_requested": false, + "status": "pending_ownership", + "dns_provider": "enom", + "rate_limited_until": null, + "certificates": [ + { + "source": "custom", + "status": "pending_ownership", + "created_at": "2024-01-15T10:30:00Z", + "expires_at": "2039-01-15T10:30:00Z", + "issuer": "Cloudflare Origin Certificate", + "issued": [] + } + ], + "validation": { + "dns_configured": false, + "alpn_configured": false, + "http_configured": false, + "ownership_txt_configured": false + }, + "dns_requirements": { + "a": ["137.66.XXX.XXX"], + "aaaa": ["2a09:8280:1::XXXX"], + "cname": "my-app-name.fly.dev", + "acme_challenge": { + "name": "_acme-challenge.example.com", + "target": "example.com.XXXXX.flydns.net" + }, + "ownership": { + "name": "_fly-ownership.example.com", + "app_value": "app-XXXXXXXXXX", + "org_value": "org-XXXXXXXXXX" + } + }, + "validation_errors": [] +} + <% end %> +
+
+ +## Get certificate details + +`GET /apps/{app_name}/certificates/{hostname}` + +Get detailed information about a hostname's certificates, including validation status, DNS requirements, and any validation errors. + +For wildcard hostnames, URL-encode the `*` character (e.g. `%2A.example.com`). + +
+
+ <% api_info = ApiInfoComponent.new( + heading: 'Path parameters', + name: 'app_name', + type: 'string', + required: true, + description: 'The name of the Fly App.' + ) %> + <% api_info.add_info( + name: 'hostname', + type: 'string', + required: true, + description: 'The hostname to get certificate details for.' + ) %> + <%= render(api_info) %> + <%= render(ApiInfoComponent.new( + heading: 'Responses', + name: '200', + description: 'OK' + )) %> +
+
+ <%= render(CodeToggleComponent.new(badge: 'GET', title: '/v1/apps/\{app_name\}/certificates/\{hostname\}')) do |component| %> + <% component.with_curl do %> + curl -i -X GET \ + -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \ + "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates/example.com" + <% end %> + <% end %> + <%= render(CodeSampleComponent.new(title: 'Status: 200 OK - Example response', language: 'json')) do %> +{ + "hostname": "example.com", + "configured": true, + "acme_requested": true, + "status": "active", + "dns_provider": "enom", + "rate_limited_until": null, + "certificates": [ + { + "source": "custom", + "status": "active", + "created_at": "2024-01-15T10:30:00Z", + "expires_at": "2039-01-15T10:30:00Z", + "issuer": "Cloudflare Origin Certificate", + "issued": [] + }, + { + "source": "fly", + "status": "active", + "created_at": "2024-01-15T12:00:00Z", + "expires_at": "2024-04-15T12:00:00Z", + "issuer": null, + "issued": [ + { + "type": "ecdsa", + "expires_at": "2024-04-15T12:00:00Z", + "certificate_authority": "lets_encrypt" + } + ] + } + ], + "validation": { + "dns_configured": true, + "alpn_configured": true, + "http_configured": true, + "ownership_txt_configured": true + }, + "dns_requirements": { + "a": ["137.66.XXX.XXX"], + "aaaa": ["2a09:8280:1::XXXX"], + "cname": "my-app-name.fly.dev", + "acme_challenge": { + "name": "_acme-challenge.example.com", + "target": "example.com.XXXXX.flydns.net" + }, + "ownership": { + "name": "_fly-ownership.example.com", + "app_value": "app-XXXXXXXXXX", + "org_value": "org-XXXXXXXXXX" + } + }, + "validation_errors": [] +} + <% end %> +
+
+ +## Check certificate status + +`POST /apps/{app_name}/certificates/{hostname}/check` + +Trigger a fresh DNS validation check for a hostname. Returns the same details as the get endpoint, plus actual DNS records resolved for the hostname. Use this to diagnose DNS configuration issues. + +
+
+ <% api_info = ApiInfoComponent.new( + heading: 'Path parameters', + name: 'app_name', + type: 'string', + required: true, + description: 'The name of the Fly App.' + ) %> + <% api_info.add_info( + name: 'hostname', + type: 'string', + required: true, + description: 'The hostname to check.' + ) %> + <%= render(api_info) %> + <%= render(ApiInfoComponent.new( + heading: 'Responses', + name: '200', + description: 'OK' + )) %> +
+
+ <%= render(CodeToggleComponent.new(badge: 'POST', title: '/v1/apps/\{app_name\}/certificates/\{hostname\}/check')) do |component| %> + <% component.with_curl do %> + curl -i -X POST \ + -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \ + "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates/example.com/check" + <% end %> + <% end %> + <%= render(CodeSampleComponent.new(title: 'Status: 200 OK - Example response', language: 'json')) do %> +{ + "hostname": "example.com", + "configured": true, + "acme_requested": true, + "status": "active", + "dns_provider": "enom", + "rate_limited_until": null, + "certificates": [ + { + "source": "fly", + "status": "active", + "created_at": "2024-01-15T12:00:00Z", + "expires_at": "2024-04-15T12:00:00Z", + "issuer": null, + "issued": [ + { + "type": "ecdsa", + "expires_at": "2024-04-15T12:00:00Z", + "certificate_authority": "lets_encrypt" + } + ] + } + ], + "validation": { + "dns_configured": true, + "alpn_configured": true, + "http_configured": true, + "ownership_txt_configured": true + }, + "dns_requirements": { + "a": ["137.66.XXX.XXX"], + "aaaa": ["2a09:8280:1::XXXX"], + "cname": "my-app-name.fly.dev", + "acme_challenge": { + "name": "_acme-challenge.example.com", + "target": "example.com.XXXXX.flydns.net" + }, + "ownership": { + "name": "_fly-ownership.example.com", + "app_value": "app-XXXXXXXXXX", + "org_value": "org-XXXXXXXXXX" + } + }, + "validation_errors": [], + "dns_records": { + "a": ["137.66.XXX.XXX"], + "aaaa": ["2a09:8280:1::XXXX"], + "cname": [], + "resolved_addresses": ["137.66.XXX.XXX", "2a09:8280:1::XXXX"], + "soa": "ns1.example.com", + "acme_challenge_cname": "example.com.XXXXX.flydns.net", + "ownership_txt": "app-XXXXXXXXXX" + } +} + <% end %> +
+
+ +## Delete hostname and all certificates + +`DELETE /apps/{app_name}/certificates/{hostname}` + +Remove a hostname and all associated certificates (both ACME and custom) from the app. + +
+
+ <% api_info = ApiInfoComponent.new( + heading: 'Path parameters', + name: 'app_name', + type: 'string', + required: true, + description: 'The name of the Fly App.' + ) %> + <% api_info.add_info( + name: 'hostname', + type: 'string', + required: true, + description: 'The hostname to remove.' + ) %> + <%= render(api_info) %> + <%= render(ApiInfoComponent.new( + heading: 'Responses', + name: '204', + description: 'no content' + )) %> +
+
+ <%= render(CodeToggleComponent.new(badge: 'DELETE', title: '/v1/apps/\{app_name\}/certificates/\{hostname\}')) do |component| %> + <% component.with_curl do %> + curl -i -X DELETE \ + -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \ + "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates/example.com" + <% end %> + <% component.with_json do %> + no body + <% end %> + <% component.with_json_table do %> + | Property | Type | Required | Description | + | --- | --- | --- | --- | + | no body | | | | + <% end %> + <% end %> + <%= render(CodeSampleComponent.new(title: 'Status: 204 no content', language: 'json')) do %> + no body + <% end %> +
+
+ +## Delete ACME certificates + +`DELETE /apps/{app_name}/certificates/{hostname}/acme` + +Stop ACME certificate issuance for a hostname. If a custom certificate exists, it will continue to be served. The hostname itself is not removed. + +
+
+ <% api_info = ApiInfoComponent.new( + heading: 'Path parameters', + name: 'app_name', + type: 'string', + required: true, + description: 'The name of the Fly App.' + ) %> + <% api_info.add_info( + name: 'hostname', + type: 'string', + required: true, + description: 'The hostname to stop ACME issuance for.' + ) %> + <%= render(api_info) %> + <%= render(ApiInfoComponent.new( + heading: 'Responses', + name: '200', + description: 'OK' + )) %> +
+
+ <%= render(CodeToggleComponent.new(badge: 'DELETE', title: '/v1/apps/\{app_name\}/certificates/\{hostname\}/acme')) do |component| %> + <% component.with_curl do %> + curl -i -X DELETE \ + -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \ + "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates/example.com/acme" + <% end %> + <% component.with_json do %> + no body + <% end %> + <% component.with_json_table do %> + | Property | Type | Required | Description | + | --- | --- | --- | --- | + | no body | | | | + <% end %> + <% end %> + <%= render(CodeSampleComponent.new(title: 'Status: 200 OK - Example response', language: 'json')) do %> +{ + "hostname": "example.com", + "configured": true, + "acme_requested": false, + "status": "active", + "certificates": [ + { + "source": "custom", + "status": "active", + "created_at": "2024-01-15T10:30:00Z", + "expires_at": "2039-01-15T10:30:00Z", + "issuer": "Cloudflare Origin Certificate", + "issued": [] + } + ], + "validation": { + "dns_configured": true, + "alpn_configured": false, + "http_configured": false, + "ownership_txt_configured": true + }, + "dns_requirements": { "..." : "..." }, + "validation_errors": [] +} + <% end %> +
+
+ +## Delete custom certificate + +`DELETE /apps/{app_name}/certificates/{hostname}/custom` + +Remove the custom certificate for a hostname. If ACME certificates are configured, they will continue to be served. The hostname itself is not removed. + +
+
+ <% api_info = ApiInfoComponent.new( + heading: 'Path parameters', + name: 'app_name', + type: 'string', + required: true, + description: 'The name of the Fly App.' + ) %> + <% api_info.add_info( + name: 'hostname', + type: 'string', + required: true, + description: 'The hostname to remove the custom certificate from.' + ) %> + <%= render(api_info) %> + <%= render(ApiInfoComponent.new( + heading: 'Responses', + name: '200', + description: 'OK' + )) %> +
+
+ <%= render(CodeToggleComponent.new(badge: 'DELETE', title: '/v1/apps/\{app_name\}/certificates/\{hostname\}/custom')) do |component| %> + <% component.with_curl do %> + curl -i -X DELETE \ + -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \ + "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates/example.com/custom" + <% end %> + <% component.with_json do %> + no body + <% end %> + <% component.with_json_table do %> + | Property | Type | Required | Description | + | --- | --- | --- | --- | + | no body | | | | + <% end %> + <% end %> + <%= render(CodeSampleComponent.new(title: 'Status: 200 OK - Example response', language: 'json')) do %> +{ + "hostname": "example.com", + "configured": true, + "acme_requested": true, + "status": "active", + "certificates": [ + { + "source": "fly", + "status": "active", + "created_at": "2024-01-15T12:00:00Z", + "expires_at": "2024-04-15T12:00:00Z", + "issuer": null, + "issued": [ + { + "type": "ecdsa", + "expires_at": "2024-04-15T12:00:00Z", + "certificate_authority": "lets_encrypt" + } + ] + } + ], + "validation": { + "dns_configured": true, + "alpn_configured": true, + "http_configured": true, + "ownership_txt_configured": true + }, + "dns_requirements": { "..." : "..." }, + "validation_errors": [] +} + <% end %> +
+
+ +## Related topics + +- [Custom domains](/docs/networking/custom-domain/) +- [Using Cloudflare with Fly.io](/docs/networking/understanding-cloudflare/) +- [Working with the Machines API](/docs/machines/api/working-with-machines-api/) +- [Apps resource](/docs/machines/api/apps-resource/) reference +- [Machines resource](/docs/machines/api/machines-resource/) reference +- [OpenAPI specification](https://docs.machines.dev/+external) diff --git a/machines/api/index.html.md b/machines/api/index.html.md index 8e378486e5..6c4af6e079 100644 --- a/machines/api/index.html.md +++ b/machines/api/index.html.md @@ -19,6 +19,8 @@ The Fly Machines REST API provides resources to provision and manage Fly Apps, F * **[Apps resource](/docs/machines/api/apps-resource):** Create and manage Fly Apps to group and administer your Machines. +* **[Certificates resource](/docs/machines/api/certificates-resource):** Manage SSL/TLS certificates for custom domains. + * **[Tokens resource](/docs/machines/api/tokens-resource):** Request an OpenID Connect token from a 3rd-party. * **[Volumes resource](/docs/machines/api/volumes-resource):** Create and manage persistent storage volumes for your Machines. diff --git a/networking/custom-domain-api.html.markerb b/networking/custom-domain-api.html.markerb deleted file mode 100644 index d334f11a57..0000000000 --- a/networking/custom-domain-api.html.markerb +++ /dev/null @@ -1,271 +0,0 @@ ---- -title: Automate the certificate process for custom domains -layout: docs -nav: firecracker ---- - -This document provides examples of using Fly.io GraphQL API queries and mutations to automate the certificates process, which is useful when you need to issue multiple certificates automatically, for example, if you have individual apps per user with custom domains. - -To add a custom domain to a single app, see [Use a custom domain](/docs/networking/custom-domain/). - -To illustrate how to automate the certificates API, we are going to show the flyctl command and the equivalent GraphQL request, wrapped in a compact easy-to-read Node application from our [fly-examples/hostnamesapi](https://github.com/fly-apps/hostnamesapi) repository. - -## GraphQL API Notes - -**Endpoints**: The Endpoint for the Fly.io GraphQL API is `https://api.fly.io/graphql`. - -**Authentication**: All queries require an API token which be obtained by signing into the [dashboard](https://fly.io/dashboard/apps/), selecting **Account > Settings > Access Tokens > Create Access Token**. Create a new token and carefully note the value; we suggest placing it in an environment variable such as `FLY_API_TOKEN` so it can be passed to applications. When used with the API, the token should be passed in an `Authorization` header with the value `Bearer `. - -**IDs and Names**: Applications can be referred to by name or by ID. Currently the Application ID and Name are interchangeable. This will change in a future semantically significant version. To see how to query for ID and Name, see [getappbyid.js](https://github.com/fly-apps/hostnamesapi/blob/master/getappbyid.js) and [getappbyname.js](https://github.com/fly-apps/hostnamesapi/blob/master/getappbyname.js) in the example repository. - -## Listing all the hosts of an application - -**With flyctl**: `fly certs list` - -**With GraphQL**: The example is in the repository as [getcerts.js](https://github.com/fly-apps/hostnamesapi/blob/master/getcerts.js). This request takes the application name as a parameter. - -```graphql -query($appName: String!) { - app(name: $appName) { - certificates { - nodes { - createdAt - hostname - clientStatus - } - } - } -} -``` - -**Example output**: - -```json -{ - "app": { - "certificates": { - "nodes": [ - { - "createdAt": "2020-03-04T14:17:14Z", - "hostname": "example.com", - "clientStatus": "Ready" - }, - { - "createdAt": "2020-03-05T15:28:41Z", - "hostname": "exemplum.com", - "clientStatus": "Ready" - } - ] - } - } -} -``` - -This lists every host associated with the application. Each host may have up to two (RSA/ECDSA) certificates associated with it. See "[Reading a certificate from an application](#reading-a-certificate-from-an-application)" to see how to query the certificates associated with the hostnames. - -## Creating a certificate for an application - -**With flyctl**: `fly certs add ` - -**With GraphQL**: The example is [addcert.js](https://github.com/fly-apps/hostnamesapi/blob/master/getcerts.js). This request takes the application id and hostname as parameters. - -```graphql -mutation($appId: ID!, $hostname: String!) { - addCertificate(appId: $appId, hostname: $hostname) { - certificate { - configured - acmeDnsConfigured - acmeAlpnConfigured - isAcmeHttpConfigured - certificateAuthority - certificateRequestedAt - dnsProvider - dnsValidationInstructions - dnsValidationHostname - dnsValidationTarget - hostname - id - source - } - } -} -``` - - -**Example Output**: - -```json -{ - "addCertificate": { - "certificate": { - "configured": true, - "acmeDnsConfigured": true, - "acmeAlpnConfigured": true, - "isAcmeHttpConfigured": true, - "certificateAuthority": "lets_encrypt", - "certificateRequestedAt": "2020-03-06T12:26:36Z", - "dnsProvider": "enom", - "dnsValidationInstructions": "CNAME _acme-challenge.codepope.wtf => example.com.o055.flydns.net.", - "dnsValidationHostname": "_acme-challenge.example.com", - "dnsValidationTarget": "example.com.o055.flydns.net", - "hostname": "example.com", - "id": "LO7FgYIy0sBZC8yuGNFKQH4QCq4ujMfJZumJCVNiQxhMq", - "source": "fly" - } - } -} -``` - -The returned data here includes all the values needed to configure DNS records for pre-traffic validation. - -## Reading a certificate from an application - -**With flyctl**: `fly certs show hostname` - -**With GraphQL**: The example is [getcert.js](https://github.com/fly-apps/hostnamesapi/blob/master/getcert.js). This request takes the application name and hostname as parameters. - -```graphql -query($appName: String!, $hostname: String!) { - app(name: $appName) { - certificate(hostname: $hostname) { - configured - acmeDnsConfigured - acmeAlpnConfigured - isAcmeHttpConfigured - certificateAuthority - createdAt - dnsProvider - dnsValidationInstructions - dnsValidationHostname - dnsValidationTarget - hostname - id - source - clientStatus - issued { - nodes { - type - expiresAt - } - } - } - } -} -``` - -**Example Output**: - -```json -{ - "app": { - "certificate": { - "configured": true, - "acmeDnsConfigured": true, - "acmeAlpnConfigured": true, - "isAcmeHttpConfigured": true, - "certificateAuthority": "lets_encrypt", - "createdAt": "2020-03-04T17:17:39Z", - "dnsProvider": "enom", - "dnsValidationInstructions": "CNAME _acme-challenge.example.com => example.com.o055.flydns.net.", - "dnsValidationHostname": "_acme-challenge.example.com", - "dnsValidationTarget": "example.com.o055.flydns.net", - "hostname": "example.com", - "id": "4n8ikGIzjsm0s5VclwTDaSODtveu2xCZkHkKCaRilafGk", - "source": "fly", - "clientStatus": "Ready", - "issued": { - "nodes": [ - { - "type": "ecdsa", - "expiresAt": "2020-06-02T16:17:51Z" - }, - { - "type": "rsa", - "expiresAt": "2020-06-02T16:17:45Z" - } - ] - } - } - } -} -``` - -Most of the output duplicates the details from adding a certificate, including the DNS validation settings. The difference here is the `issued` section which contains an array of nodes, each of which is the type and expiry date of certificates that have been issued against this host name. Here we see that ECSDA and RSA certificates have been issued. - -## Checking a certificate - -**With flyctl**: `fly certs check hostname` - -**With GraphQL**: The example is [checkcert.js](https://github.com/fly-apps/hostnamesapi/blob/master/checkcert.js). This request takes the application name and hostname as parameters. It is essentially the same as reading the certificate, but the presence of a request for the certificate's check value will start a validation process. The output is similar too. - -```graphql -query($appName: String!, $hostname: String!) { - app(name: $appName) { - certificate(hostname: $hostname) { - check - configured - acmeDnsConfigured - acmeAlpnConfigured - isAcmeHttpConfigured - certificateAuthority - createdAt - dnsProvider - dnsValidationInstructions - dnsValidationHostname - dnsValidationTarget - hostname - id - source - clientStatus - issued { - nodes { - type - expiresAt - } - } - } - } -} -``` - -## Deleting a certificate - -**With flyctl**: `fly certs delete hostname` - -**With GraphQL**: The example is [deletecert.js](https://github.com/fly-apps/hostnamesapi/blob/master/deletecert.js). This request takes the application name and hostname as parameters and will remove the hostname from the application. - -```graphql - mutation($appId: ID!, $hostname: String!) { - deleteCertificate(appId: $appId, hostname: $hostname) { - app { - name - } - certificate { - hostname - id - } - } - } -``` - -The GraphQL mutation returns the app name and the hostname and certificate id that was removed. - -**Example Output**: - -```json -{ - "deleteCertificate": { - "app": { - "name": "nginxproxy" - }, - "certificate": { - "hostname": "example.com", - "id": "6AZc4AS6ysZgHPqFg7TGzIyofewuZnToxu6lUbBi8DHGQ" - } - } -} -``` - -## Related topics - -- [Custom domains](/docs/networking/custom-domain/) diff --git a/networking/custom-domain.html.markerb b/networking/custom-domain.html.markerb index 5059e92154..19b79edd9d 100644 --- a/networking/custom-domain.html.markerb +++ b/networking/custom-domain.html.markerb @@ -30,11 +30,7 @@ For example, using the CLI: fly certs add example.com ``` -This command will show you the applicable DNS configuration options for your setup, including: -- A and AAAA records -- CNAME records -- Proxy setup (if using a CDN) -- DNS-01 challenge (required for wildcards, optional for pre-traffic certificate generation) +This command will show you the applicable DNS configuration options for your setup. If you are adding a wildcard domain with the CLI, put quotes around the hostname to avoid shell expansion: @@ -42,7 +38,7 @@ If you are adding a wildcard domain with the CLI, put quotes around the hostname fly certs add "*.example.com" ``` -Use `fly certs check ` to check the certificate status and validation progress, or `fly certs setup ` to view the setup instructions again. +Use `fly certs check ` to check the certificate status and validation progress, or `fly certs setup ` to view all setup options. ### Configure DNS records @@ -50,38 +46,41 @@ Now that you've added your domain to your app, configure DNS records with your D Choose the DNS setup that matches your needs: +
+**Important:** To issue or renew a certificate, Fly.io needs to verify your domain through at least one of: an IPv6 address (AAAA record) pointing to your app, a [CNAME `_acme-challenge`](#dns-challenge), or a [`_fly-ownership` TXT record](#domain-ownership-verification). +
+ #### A and AAAA records (recommended) Use A and AAAA records for most direct connections to your app. These records point your domain directly to your app's IP addresses. -The A and AAAA records you need to set will be shown in your dashboard, and in the output from `fly certs add`. If your app doesn't have an IPv6 address, allocate one with `fly ips allocate-v6`. - -
-**Important:** Hostname validation will fail without an IPv6 address—and we won't attempt to issue or renew a certificate—unless you're using a [CNAME `_acme-challenge` for domain verification](#dns-challenge). However, we still recommend having both an IPv4 and an IPv6 address allocated if your app is serving traffic. -
+The A and AAAA records you need to set will be shown in your dashboard, and in the output from `fly certs add`. If your app doesn't have IPv4 and IPv6 addresses, allocate them with `fly ips allocate`. #### CNAME records CNAME records work well for subdomains (like `www.example.com` or `app.example.com`). A CNAME points your custom domain at a unique `.fly.dev` hostname for your app. CNAME records are also a good option if you have many IP addresses assigned to your app, or expect to change them in the future. -Set the CNAME record with your DNS provider. Each app has a unique CNAME target that will be shown in the dashboard for your certificate, or in the output from `fly certs add`. +Set the CNAME record with your DNS provider. Each app has a unique CNAME target that will be shown in the dashboard for your certificate, or in the output from `fly certs setup`. Setting CNAME records for the apex domain can be problematic, and should be avoided unless your DNS provider supports CNAME flattening. Some providers use a special name for these records, such as ANAME or ALIAS, and some will flatten a CNAME at the apex automatically. In general, we recommend setting A/AAAA records on the apex domain. -#### Proxy/CDN setup +#### DNS Challenge + +Use the DNS-01 challenge when you need a wildcard certificate, or when you +want to generate the certificate before directing traffic to your app. -If you're using a proxy or CDN (like Cloudflare) in front of your Fly application, configure only an AAAA record pointing to your app's IPv6 address, as certificate generation requires IPv6 traffic. +This will require setting an `_acme-challenge` CNAME record on your domain. The required record will be shown in your dashboard, or in the output from `fly certs setup`. -#### DNS Challenge +#### Domain ownership verification -Use the DNS-01 challenge when: +Use a `_fly-ownership` TXT record to verify domain ownership when other validation methods aren't available for your setup. This is useful when: -- You need a wildcard certificate -- You want to generate the certificate before directing traffic to your app -- Automatic validation methods don't work for your setup +- Your domain is behind a CDN or proxy that prevents direct validation +- You're importing a [custom certificate](#use-your-own-certificate) +- You don't want to allocate or use IPv6 addresses -This will require setting an `_acme-challenge` CNAME record on your domain. The required record will be shown in your dashboard, or in the output from `fly certs add`. +The required record will be shown in your dashboard, or in the output from `fly certs setup`. You can include multiple values in a single TXT record by separating them with semicolons. ### Certificate validation @@ -89,16 +88,31 @@ After you have configured your DNS, Fly.io automatically validates your domain o - **TLS-ALPN challenge**: Validates through a TLS handshake with Fly Proxy. This is the preferred method and works automatically for direct connections. -- **HTTP-01 challenge**: Validates by requesting a specific file from your domain. Used automatically when TLS-ALPN isn't available, such as with proxy/CDN configurations. +- **HTTP-01 challenge**: Validates by requesting a specific file from your domain. Used automatically when TLS-ALPN isn't available. - **DNS-01 challenge**: You manually add a DNS record to validate domain ownership. Required for wildcard certificates, or to generate certificates before directing traffic to your app. +## Use your own certificate + +In addition to the automatic Let's Encrypt certificates that Fly.io manages for you, you can upload your own TLS certificate and private key. Upload your certificate and private key in PEM format using the CLI: + +```cmd +fly certs import example.com --fullchain fullchain.pem --private-key private-key.pem +``` + +You can also import certificates from your app dashboard under **Certificates**. + +After uploading a custom certificate, it may show a status of `pending_ownership` until domain ownership is verified. +Ownership is verified by an IPv6 DNS record pointing at your app, or by the `_fly-ownership` TXT record. + +Custom and Fly-managed (Let's Encrypt) certificates can coexist for the same hostname. When both are present, your custom certificate is served as the primary certificate, and the Fly-managed certificate acts as an automatic fallback. + ## Other `fly cert` commands * `fly certs list` - List the certificates associated with an app. * `fly certs check ` - Trigger a check on the domain validation and DNS configuration for the given hostname and return results in the same format as `fly certs show`. * `fly certs setup ` - Shows setup instructions for configuring DNS records for an existing certificate. -* `fly certs remove ` - Remove a certificate from an app for the given hostname. +* `fly certs remove ` - Remove a certificate from an app for the given hostname. Use `--custom` to remove only a custom certificate, or `--acme` to stop ACME issuance only. ## Teaching your app about custom domains @@ -138,18 +152,19 @@ If you're building a platform on top of Fly.io, and you expect that your users w ### I use Cloudflare, and there seems to be a problem issuing or validating my Fly.io TLS certificate -If you're using Cloudflare's proxying feature (orange cloud), Fly.io can generate a certificate using the HTTP-01 challenge. +If you're using Cloudflare's proxying feature (orange cloud), verify domain ownership by adding a [`_fly-ownership` TXT record](#domain-ownership-verification). Once ownership is verified, Fly.io can issue certificates using the HTTP-01 challenge through Cloudflare's proxy. For this to work: -1. **Configure DNS records properly**: Create only an AAAA record pointing to your app's IPv6 address. Do not create an A record or CNAME when using Cloudflare's proxy. +1. **Verify domain ownership**: Add a `_fly-ownership` TXT record with the values shown in your certificate details. See [Domain ownership verification](#domain-ownership-verification). 2. **Set SSL mode**: Use "Full" or "Full (Strict)" SSL mode in Cloudflare. "Flexible" mode can cause redirect loops. -Alternatively, you can use the [DNS-01 challenge method](#dns-challenge) instead of the automatic validation method, though this may conflict with Cloudflare's own certificate issuance. +Depending on your Cloudflare configuration, HTTP-01 challenges may be blocked and generation may still fail. +In these cases, you can bypass ACME certificate generation entirely by importing a [Cloudflare Origin Certificate](/docs/networking/understanding-cloudflare/#using-a-cloudflare-origin-certificate) and verifying ownership with the same `_fly-ownership` TXT record. ## Related reading - [Networking overview](/docs/networking/) Broader look at how Fly.io handles routing, DNS, IPs, and certificates. -- [Automate the certificate process for custom domains](/docs/networking/custom-domain-api/) Use Fly.io's GraphQL API to automate cert provisioning and domain verification. +- [Certificates API reference](/docs/machines/api/certificates-resource/) Use the Machines API to automate cert provisioning and domain verification. - [TLS termination by Fly Proxy](/docs/security/tls-termination/) — How Fly.io handles HTTPS for your apps and manages TLS certificates automatically behind the scenes. - [Understanding Cloudflare](/docs/networking/understanding-cloudflare/) What happens when you put Fly.io behind Cloudflare, and how to avoid common pitfalls. diff --git a/networking/index.html.markerb b/networking/index.html.markerb index f313618ef1..ffb535dde5 100644 --- a/networking/index.html.markerb +++ b/networking/index.html.markerb @@ -21,15 +21,13 @@ Networking on Fly.io. - **[Flycast - Private Fly Proxy Services](/docs/networking/flycast):** Route requests to private apps through Fly Proxy to take advantage of features like load balancing and autostop/autostart based on traffic. - **[Egress IP Addresses](/docs/networking/egress-ips/):** How to get stable outbound IPs from your Fly apps using machine-scoped assignments or a shared proxy setup. - + - **[Dynamic request routing](/docs/networking/dynamic-request-routing/):** Use Fly.io request and response headers to customize request routing to regions, apps, and even specific Machines. - **[Custom domains](/docs/networking/custom-domain/):** Add a custom domain for your app and troubleshoot certificate creation. - **[Understanding Cloudflare](/docs/networking/understanding-cloudflare/):** How to use Cloudflare on Fly.io. -- **[Automate the certificate process with the GraphQL API](/docs/networking/custom-domain-api):** Issue multiple certificates automatically for custom domains with the GraphQL API. - - **[HTTP request headers](/docs/networking/request-headers/):** Fly.io-specific and standard HTTP headers added by the HTTP connection handler. - **[Run UDP services](/docs/networking/udp-and-tcp/):** How to set up apps that use UDP. diff --git a/networking/services.html.markerb b/networking/services.html.markerb index 5467bf074a..2d42a8728a 100644 --- a/networking/services.html.markerb +++ b/networking/services.html.markerb @@ -85,7 +85,7 @@ The following steps should allow you to switch an app with a custom domain from 1. Add a shared IPv4 address with `flyctl ips allocate-v4 --shared`. 2. If you don't use a CNAME to `.fly.dev`, [add the shared IPv4 as an A record](/docs/networking/custom-domain/#set-the-a-record). 3. Confirm your app works via shared IP: `curl -Iv http:// --resolve :80:`. You should receive a 301 redirect response. -4. If you are using a custom domain and don't already have an SSL certificate for this app, [create one](/docs/networking/custom-domain/#get-certified). The cert is required for routing to your app via the custom domain, even for HTTP connections. +4. If you are using a custom domain and don't already have an SSL certificate for this app, [create one](/docs/networking/custom-domain/#add-your-domain-to-your-app). The cert is required for routing to your app via the custom domain, even for HTTP connections. 5. Wait for DNS caches to clear. Five minutes is likely enough, but this varies wildly. This is determined by the larger of your DNS record’s TTL and that of our `.fly.dev` record. 6. Remove the dedicated IPv4 from the app using `fly ips release `. You can view your app's IP addresses using `fly ips list`. 7. Remove the unwanted IPv4 address from your DNS if it was set manually as an A record. diff --git a/networking/understanding-cloudflare.html.md b/networking/understanding-cloudflare.html.md index 5bb2500f6e..849f2f8495 100644 --- a/networking/understanding-cloudflare.html.md +++ b/networking/understanding-cloudflare.html.md @@ -23,84 +23,114 @@ This is the simplest and most reliable way to use Cloudflare with Fly.io. To con ## CDN proxy setup ("orange cloud") -Enabling Cloudflare's proxy gives you caching and DDoS protection, but it also changes how SSL certificates work. Cloudflare terminates TLS traffic, which interferes with Fly.io's default TLS-ALPN-01 certificate issuance process. +Enabling Cloudflare's proxy gives you caching and DDoS protection, but it also changes how SSL certificates work. Cloudflare terminates TLS traffic, which means Fly.io can't use its default TLS-ALPN-01 certificate issuance process. The key to a smooth setup is a `_fly-ownership` TXT record that proves domain ownership to Fly.io and allows certificate validation to work through Cloudflare's proxy. -The recommended approach for using Cloudflare's CDN proxy is to configure it to forward HTTP requests, which allows HTTP-01 challenges to work properly. To configure a CDN proxy setup: - -1. Create an `AAAA` record only pointing to your Fly.io app's IPv6 address. -2. Do not add `A` or `CNAME` records for the hostname. If you previously had an `A` record pointing elsewhere (such as a legacy server or placeholder), remove it, even if the correct `AAAA` record is present. Having any `A` record alongside the `AAAA` can confuse Let’s Encrypt validation and prevent certificate renewal. -3. Enable the Cloudflare proxy (orange cloud). -4. Set SSL mode in Cloudflare to Full (strict). -5. Enable Always Use HTTPS in Cloudflare. - -
-**Important:** This setup allows Fly.io to handle HTTP-01 validation and issue certificates automatically. +
+**Important:** Wildcard certificates (e.g. `*.example.com`) cannot be issued automatically through Cloudflare's proxy, because Let's Encrypt requires a DNS-01 challenge for wildcards and Cloudflare's Universal SSL can interfere with it. To use a wildcard certificate behind Cloudflare's proxy, import a [Cloudflare Origin Certificate](#using-a-cloudflare-origin-certificate) instead.
+### Recommended setup -### Using the DNS-01 challenge (manual certificate setup) +1. Add your domain to your Fly app: -If the HTTP-01 challenge doesn't work for your setup, you can fall back to using a DNS-01 challenge to manually issue a certificate. +```bash +fly certs add example.com +``` -To do this: -1. Use the Fly.io dashboard or run: +2. Add a `_fly-ownership` TXT record in Cloudflare DNS. Run `fly certs setup` to see the required record: ```bash -fly certs create +fly certs setup example.com ``` -2. Add the required TXT records to Cloudflare when prompted. -3. The certificate will issue once DNS propagation is complete. +Add the TXT record shown in the output. This record proves domain ownership to Fly.io. + +3. Configure your DNS records in Cloudflare: + - Add an `A` record pointing to your Fly app's IPv4 address. + - Add an `AAAA` record pointing to your Fly app's IPv6 address. + - Alternatively, use a `CNAME` record pointing to your app's `.fly.dev` target. + - Enable the Cloudflare proxy (orange cloud) for these records. + +4. Set SSL mode in Cloudflare to **Full (strict)**. -### How Fly.io handles TLS and certificate management +5. Enable **Always Use HTTPS** in Cloudflare (recommended). -TLS certificates are provisioned automatically using Let’s Encrypt. We handle renewals in advance and manage rate limits carefully per hostname, so you don’t need to worry about expiration dates or throttling. +Fly.io will automatically issue a Let's Encrypt certificate via the HTTP-01 challenge, which works through Cloudflare's proxy when the ownership TXT record is in place. Run `fly certs check example.com` to monitor validation progress. -We don’t currently support bringing your own ACME provider like ZeroSSL or SSL.com into our provisioning flow. If you prefer to terminate TLS yourself and handle your own ACME HTTP challenges, you can do that by passing TCP through to your Fly app. The Fly Proxy won’t interfere with these challenges, and there's no IPv6 requirement if you're managing this independently. +Some Cloudflare configurations will block HTTP-01 challenges altogether. If certificate issuance is not progressing, import a [Cloudflare Origin Certificate](#using-a-cloudflare-origin-certificate) instead. -Both approaches are valid, but we recommend using the platform's built-in TLS termination and certificate management, especially as certificate validity periods get shorter. +### Using a Cloudflare Origin Certificate + +If automatic certificate issuance doesn't work for your setup, or if you need a wildcard certificate (like `*.example.com`), you can import a Cloudflare Origin Certificate instead. + +1. Generate a Cloudflare Origin Certificate in the Cloudflare dashboard: + - Go to SSL/TLS > Origin Server > Create Certificate. + - Choose the hostnames to cover (for example, `example.com` and `*.example.com`). + - Choose a validity period (Cloudflare offers up to 15 years). + - Keep the page open — Cloudflare only shows the private key once. + +2. Import the certificate to Fly.io in your app dashboard under **Certificates**. You can paste the certificate and private key directly from Cloudflare. Alternatively, save them as files and use the CLI: + +```bash +fly certs import example.com --fullchain cert.pem --private-key key.pem +fly certs import "*.example.com" --fullchain cert.pem --private-key key.pem +``` + +3. Add the `_fly-ownership` TXT record shown in the certificate setup details, if you haven't already set one up. + +4. Configure your DNS records and Cloudflare SSL mode as described in the [recommended setup](#recommended-setup) above. + +
+Custom and ACME certificates can coexist for the same hostname. The custom certificate is served as primary, and any ACME certificate acts as an automatic fallback. +
## Common issues to watch for -- Cloudflare's Universal SSL may interfere with DNS-01 challenges. Disable it or use HTTP-01 instead to avoid this. +### General issues + +- Always use "Full (strict)" in Cloudflare when connecting to Fly.io. Using "Flexible" mode may cause redirect loops. +- Only one application should manage certificates for a subdomain or apex domain. Using more than one can cause conflicts. + +### ACME-specific issues + +These issues apply when using Let's Encrypt (ACME) certificates behind Cloudflare's proxy. They do not apply if you are using a [Cloudflare Origin Certificate](#using-a-cloudflare-origin-certificate). + - Check that your domain allows Let's Encrypt with a CAA record like: ``` example.com. CAA 0 issue "letsencrypt.org" ``` -- Only one application should manage certificates for a domain. Using more than one can cause conflicts. -- Let's Encrypt has limits. Check the certificate status tab in the Fly.io dashboard if issuance fails. +- Let's Encrypt has rate limits. Check the certificate status tab in the Fly.io dashboard if issuance fails. +- DNS-01 certificates will conflict with Cloudflare Universal SSL. This happens when Cloudflare's Universal SSL automatically inserts hidden ACME challenge TXT records that don't appear in your DNS dashboard. These ghost records interfere with Let's Encrypt's validation process. The best choice in this situation is to import a [Cloudflare Origin Certificate](#using-a-cloudflare-origin-certificate) instead. -
-**Cloudflare Universal SSL Ghost Records Issue:** If you're using Cloudflare and Let's Encrypt can't issue a certificate, you may be encountering phantom _acme-challenge TXT records. This happens when Cloudflare's Universal SSL automatically inserts hidden ACME challenge TXT records that don't appear in your DNS dashboard but show up when you run `dig TXT _acme-challenge.yourdomain.com`. These ghost records interfere with Let's Encrypt's validation process. +### Custom certificate issues -To resolve this: -1. Go to Edge Certificates in Cloudflare's SSL/TLS settings and disable Universal SSL. -2. Purge cache ("Purge Everything" in Cloudflare) and/or enable Development Mode. -3. Wait several minutes for DNS cache to clear, then confirm with `dig` that only your intended TXT record is present. -4. Retry certificate issuance from Fly.io. -
+- Unlike Let's Encrypt certificates, which renew automatically, custom certificates must be manually renewed and re-imported before they expire. Cloudflare Origin Certificates can be issued with long validity periods (up to 15 years), but you should still track expiry. ## Tools for debugging These tools can help when diagnosing certificate or DNS issues: +- `fly certs check `: Check ownership TXT status, custom certificate status, and ACME certificate status all in one place. This is the best starting point for debugging. - [crt.sh](https://crt.sh/): Check issued certificates. - [DNSChecker](https://dnschecker.org/): Confirm DNS propagation. -- [Let's Debug](https://letsdebug.net/): Analyze certificate validation issues. +- [Let's Debug](https://letsdebug.net/): Analyze Let's Encrypt certificate validation issues. - dig: Inspect DNS records from the command line. For example: ```bash +fly certs check your-app.example.com + dig AAAA your-app.example.com -dig TXT _acme-challenge.your-app.example.com +dig TXT _fly-ownership.your-app.example.com ``` ## Related topics -- [Custom domain guide](https://fly.io/docs/networking/custom-domain/) -- [Custom domain API reference](https://fly.io/docs/networking/custom-domain-api/) -- [flyctl certs commands](https://fly.io/docs/flyctl/certs/) +- [Custom domain guide](/docs/networking/custom-domain/) +- [Certificates API reference](/docs/machines/api/certificates-resource/) +- [flyctl certs commands](/docs/flyctl/certs/) +- [fly certs import reference](/docs/flyctl/certs-import/) diff --git a/partials/_firecracker_nav.html.erb b/partials/_firecracker_nav.html.erb index 37c6e62a50..42fadff232 100644 --- a/partials/_firecracker_nav.html.erb +++ b/partials/_firecracker_nav.html.erb @@ -139,7 +139,6 @@ { text: "Egress IP Addresses", path: "/docs/networking/egress-ips/" }, { text: "Dynamic Request Routing", path: "/docs/networking/dynamic-request-routing/" }, { text: "Custom Domains", path: "/docs/networking/custom-domain/" }, - { text: "Automate Certificates via API", path: "/docs/networking/custom-domain-api/" }, { text: "Understanding Cloudflare", path: "/docs/networking/understanding-cloudflare/" }, { text: "Request Headers", path: "/docs/networking/request-headers/" }, { text: "Run UDP Services", path: "/docs/networking/udp-and-tcp/" }, diff --git a/partials/_machines_nav.html.erb b/partials/_machines_nav.html.erb index 02f3221aff..8a13f090ea 100644 --- a/partials/_machines_nav.html.erb +++ b/partials/_machines_nav.html.erb @@ -17,6 +17,7 @@ links: [ { text: "Working with the Machines API", path: "/docs/machines/api/working-with-machines-api/" }, { text: "Apps", path: "/docs/machines/api/apps-resource/" }, + { text: "Certificates", path: "/docs/machines/api/certificates-resource/" }, { text: "Machines", path: "/docs/machines/api/machines-resource/" }, { text: "Tokens", path: "/docs/machines/api/tokens-resource/" }, { text: "Volumes", path: "/docs/machines/api/volumes-resource/" }, diff --git a/security/tls-termination.html.markerb b/security/tls-termination.html.markerb index 26f641ca0a..836571da5f 100644 --- a/security/tls-termination.html.markerb +++ b/security/tls-termination.html.markerb @@ -4,7 +4,9 @@ layout: docs nav: firecracker --- -FLy Proxy provides TLS termination by default for web apps (services accepting traffic over HTTPS on port 443). With the built-in TLS handler, the Fly Proxy verifies your app with Fly.io-managed certificates authorized through Let's Encrypt, converts your TLS connection to unencrypted TCP, and forwards traffic to your app through our secure WireGuard mesh network. +Fly Proxy provides TLS termination by default for web apps (services accepting traffic over HTTPS on port 443). With the built-in TLS handler, Fly Proxy uses your app's TLS certificates to terminate TLS connections, converts them to unencrypted TCP, and forwards traffic to your app through our secure WireGuard mesh network. Fly Proxy supports both Fly-managed certificates issued through Let's Encrypt, and your own uploaded certificates. + +To set up certificates for a custom domain, including importing your own certificates, see [Custom domains](/docs/networking/custom-domain/). You can [configure a specific TLS version and ALPN protocols](/docs/reference/configuration/#services-ports-tls_options) for your app in the `fly.toml` config file.