Neon now has realtime: Live SQL with full reactivity. Try it in Beta today
/Functions/Custom domains

Custom domains for Neon Functions

beta

Serve a Neon Function from a domain you own.

Beta

Custom domains for Neon Functions is in beta. Share your feedback on Discord or via the Neon Console.

Each Neon Function has a native invocation URL. You can also serve it from a domain you own, such as api.example.com. Neon routes the custom domain to one function on one branch and provisions its TLS certificate automatically. You don't need to change your function code.

For example, one function is reachable at both URLs:

Native:  https://br-cool-forest-a1b2c3d4-api.compute.c-2.us-east-2.aws.neon.tech
Custom:  https://api.example.com

Custom domains are branch-scoped: a domain registered on one branch isn't inherited by its child branches, and each hostname can be registered only once. Use a distinct hostname for each preview or development branch.

Neon Functions do not support hosting websites.

Secure both function URLs

Adding a custom domain doesn't authenticate the function or disable its native Neon URL. Both URLs remain publicly reachable, so protect the function with application-level authentication.

Before you start

You need:

  • A deployed Neon Function.
  • A domain you control, and access to its DNS settings.
  • Optional: The latest Neon CLI, if you want to manage the domain from the command line.
  • Optional: @neon/sdk 5.0.0 or later, if you want to manage the domain with the SDK.

Register a custom domain

  1. In the Neon Console, open your project's Settings, then Functions > Custom domains.
  2. Enter the domain you own.
  3. Select Function, then select the function to serve from the domain.
  4. Select Add custom domain.

The Console displays the CNAME target to add at your DNS provider.

entity_type is function, the only supported value today. The function is identified by its slug: the CLI --slug flag and the API entity_id field carry the same value. Re-registering the same domain for the same function returns the same result. Registering a domain already assigned to another target returns a conflict without revealing the existing owner.

Declare with neon.ts

If you manage the branch with a neon.ts policy, declare the domain on the function instead of registering it imperatively. Add customDomains to the function and run neon deploy:

neon.ts
import { defineConfig } from "@neon/config/v1";

export default defineConfig({
  functions: {
    api: {
      name: "API",
      source: "./functions/api.ts",
      customDomains: ["api.example.com"],
    },
  },
});
Neon CLI
neon deploy

neon deploy registers the domain and prints the CNAME target to configure below. The static customDomains list applies on the default branch only, since a hostname is globally unique and can't be inherited by child branches. Removing a domain from neon.ts doesn't delete its registration; delete it explicitly, as described in Delete a custom domain. For per-branch overrides and the full ruleset, see the neon.ts reference.

Configure DNS

At your DNS provider, create a CNAME record using the target returned during registration:

Use a subdomain

Standard DNS doesn't allow a CNAME record at the zone apex. Use a subdomain such as api.example.com unless your DNS provider supports CNAME flattening.

  • Type: CNAME
  • Name: Your custom domain, such as api.example.com. Some providers expect only the host label, such as api.
  • Value: Copy the exact cname_target hostname returned by Neon, such as fn-custom-domains.us-east-2.aws.neon.tech. Don't include https:// or a URL path.
  • TTL: Your provider's default.

Configure the record as DNS-only. If your provider proxies the record, Neon can't validate the domain. On Cloudflare, set the record to "DNS only" (grey cloud), not "Proxied" (orange cloud).

Remove conflicting A, AAAA, or CNAME records for the same hostname. DNS changes can take time to propagate according to the record's TTL.

Allow Let's Encrypt in CAA records

If your domain uses CAA records, authorize Let's Encrypt:

CAA 0 issue "letsencrypt.org"

Neon can't provision a certificate when an applicable CAA record blocks Let's Encrypt.

List domains and check status

In the Neon Console, open your project's Settings, then Functions > Custom domains. The table lists each domain, its target function, and its CNAME target.

Each domain reports a top-level status, plus the dns_status and binding_status that feed it.

status is:

  • pending: Neon is still checking DNS or completing internal routing setup.
  • active: DNS points to the Neon edge, CAA permits Let's Encrypt, and internal routing is in place.
  • error: DNS, CAA, or internal routing needs attention. Check status_reason.

dns_status is pending, ok, misconfigured (resolves somewhere other than the Neon edge), or caa_blocked (a CAA record forbids Let's Encrypt). binding_status is pending, present, or missing, where missing is an internal fault.

When status is error, status_reason names the cause and its fix:

status_reasonMeaningFix
cname-not-pointing-at-edgeThe hostname resolves somewhere other than the Neon edge.Check the CNAME and remove conflicting or proxied records.
caa-blocks-lets-encryptAn applicable CAA record doesn't authorize Let's Encrypt.Add CAA 0 issue "letsencrypt.org", including any CAA inherited from a parent domain.
binding-missingNeon's internal routing is unavailable.Contact Neon Support if it persists.

Status checks run asynchronously. Poll the domain every few seconds until it becomes active or reports an actionable error.

Verify HTTPS separately

active verifies DNS, CAA, and routing. It doesn't report certificate issuance state. Make an HTTPS request before using the custom domain in production:

curl -i https://api.example.com/health

The first HTTPS request can trigger certificate issuance. If DNS is correct and the API reports active, wait a minute and retry. Contact Neon Support if HTTPS continues to fail.

Runtime behavior

A request through a custom domain preserves its method, path, query, body, normal application headers, and streaming behavior. WebSockets and SSE work through the custom URL without additional configuration.

Inside the function, Request.url and the Host header use the function's native Neon hostname, not the custom hostname. To read the hostname the client requested, use the x-forwarded-host header; Neon overwrites any client-supplied value, so it's safe to trust for tenant routing.

When your neon.ts declares the function, neon env pull writes its native URL as NEON_FUNCTION_<SLUG>_BASE_URL. Store the custom URL separately in your application configuration.

If a browser calls the custom domain directly, configure CORS and authentication for that origin.

Delete a custom domain

Remove the DNS record before releasing the domain registration. This prevents a dangling CNAME from continuing to point at Neon's custom-domain edge.

  1. Remove the CNAME record at your DNS provider.
  2. Wait for public DNS to stop returning the Neon target. Check with dig +short api.example.com.
  3. Delete the registration using one of the following methods.

Under Settings > Functions > Custom domains in the Neon Console, open the actions menu (⋮) for the domain, select Delete, then select Remove domain to confirm.

Routing changes take a short time to apply everywhere. Requests can continue reaching the old function briefly after deletion, so verify that the custom URL no longer serves it before reassigning the hostname.

Deleting a function doesn't remove its custom-domain registration. Remove the domain explicitly when deleting a function.

Deleting a project doesn't release its custom domains either. If you want to reuse a hostname, delete its custom-domain registration before you delete the project. If you've already deleted the project, contact Support to release the domain.

Need help?

Join our Discord Server to ask questions or see what others are doing with Neon. For paid plan support options, see Support.

Was this page helpful?
Edit on GitHub