← Back to blog
April 7, 20265 min read

Custom Domains for AI Agent Cells

Point myapp.com to your cell in 60 seconds. Automatic SSL. Apex domains. Zero configuration.

The Problem

Every cell on OnCell gets a preview URL: dev-abc--user-123.cells.oncell.ai. Great for development. Terrible for production.

When your AI agent builds a customer-facing app, that customer needs a real domain. Not a subdomain of your platform. Their domain. With HTTPS. Without spending a week configuring Nginx, Certbot, and Route 53.

Three API Calls

We wanted custom domains to be as simple as creating a cell. Three API calls, zero infrastructure knowledge required.

import { OnCell } from "@oncell/sdk";
const oncell = new OnCell({ apiKey: "oncell_sk_..." });

// 1. Add the domain
const domain = await oncell.domains.add("myapp.com", cell.id);
// Returns DNS instructions: A record → static IPs

// 2. Verify DNS (after user configures it)
await oncell.domains.verify("myapp.com");

// 3. Provision SSL
await oncell.domains.provisionSsl("myapp.com");
// 30 seconds later: myapp.com serves your cell with HTTPS

That's it. The domain is live. Traffic flows from myapp.com to your cell. SSL auto-renews. No Nginx. No Certbot cron job. No CloudFormation templates.

How It Works Under the Hood

The architecture has three layers, each solving a specific problem.

Layer 1: Static IPs via Global Accelerator.Apex domains (myapp.com, not a subdomain) require A records — they can't use CNAME. A records need IP addresses. But our infrastructure runs behind load balancers with dynamic IPs. AWS Global Accelerator gives us two static anycast IPs that route to our NLB. Customers point their A records there. Works for both apex and subdomains.

Layer 2: Let's Encrypt via HTTP-01. When you call provisionSsl(), our API server creates a Let's Encrypt order, generates an HTTP-01 challenge token, stores it in DynamoDB, and waits for Let's Encrypt to verify it. The challenge is served on port 80 through the NLB. Once verified, we download the cert, encrypt it with KMS, and store it in DynamoDB. The whole process takes 30-60 seconds.

Layer 3: SNI-based TLS termination. Our API server runs a TLS server on port 8443. When a request comes in for myapp.com, the TLS handshake includes the hostname via SNI. Our CertificateManager looks up the cert from DynamoDB (with a 5-minute in-memory cache), decrypts it from KMS, and creates a TLS context. The decrypted connection then hits the cell proxy, which looks up myapp.com in the custom domains table to find the target cell.

Traffic flow:

  myapp.com
    → Global Accelerator (static IPs, anycast)
    → NLB (TCP passthrough, no TLS termination)
    → API Server :8443 (TLS termination, Let's Encrypt cert from DynamoDB)
    → Cell proxy (domain → cell_id lookup, 60s cache)
    → Cell runtime (Next.js, your app)

Apex Domains Without ANAME Hacks

The classic problem: myapp.com(apex) can't have a CNAME record per DNS spec. Most platforms tell you to use an ALIAS or ANAME record, which not all DNS providers support.

We use Global Accelerator static IPs. The customer adds a standard A record — supported by every DNS provider on earth. No ANAME, no ALIAS, no CloudFlare-specific CNAME flattening. Just two IP addresses in an A record.

DNS configuration:

  Apex (myapp.com):
    Type: A
    Values: 166.117.117.6, 15.197.87.164

  Subdomain (app.myapp.com):
    Type: CNAME
    Value: cells.oncell.ai

  www (www.myapp.com):
    Type: CNAME
    Value: cells.oncell.ai
    (auto-included as SAN in the SSL cert)

Blue/Green Deploys for Free

Because domains are mapped to cells (not to code or builds), you get blue/green deploys with a single API call:

// Build new version in a separate cell
const blueCell = await oncell.cells.create({ customerId: "prod-v2", ... });
// ... deploy, test, verify ...

// Swap: point domain to new cell (instant, no downtime)
await oncell.domains.reassign("myapp.com", blueCell.id);

// Old cell keeps running for rollback
// Delete it when you're confident:
await oncell.cells.delete(oldCell.id);

The reassign call updates a single DynamoDB record and invalidates the proxy cache. Next request hits the new cell. No DNS propagation delay. No load balancer drain. Instant.

Cost

Custom domains add minimal infrastructure cost:

Per domain:
  Route 53 queries:       ~$0.40/mo
  Let's Encrypt cert:     $0 (free)
  DynamoDB storage:       ~$0.01/mo
  Total:                  ~$0.41/mo

Platform (shared):
  Global Accelerator:     ~$18/mo (fixed)
  NLB:                    ~$16/mo (fixed)
  Total:                  ~$34/mo (amortized across all domains)

We charge $2/mo per custom domain. At 50+ domains, the platform cost is fully covered and each additional domain is nearly pure margin.

What's Next

Custom domains are available now in the SDK, CLI, and dashboard. Three API calls to go from cell to production domain.

Next up: automatic cert renewal (30 days before expiry), wildcard domains for multi-tenant apps, and a CDN layer for static assets. But the core is done — point a domain at a cell, get HTTPS, serve your app.

npm install @oncell/sdk

// or via CLI:
oncell domains add myapp.com --cell dev-abc--user-123
oncell domains verify myapp.com
oncell domains ssl myapp.com